mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Core data checkpoint 1, all pages load
This commit is contained in:
parent
4a08431766
commit
fd5b9eb1c3
14 changed files with 819 additions and 544 deletions
|
|
@ -9,6 +9,8 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
|
||||
DD23A51326FEF5D500D9B90C /* MessageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A51226FEF5D500D9B90C /* MessageData.swift */; };
|
||||
DD2E652427679E4000E45FC5 /* NodeInfoEntityRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E652327679E4000E45FC5 /* NodeInfoEntityRow.swift */; };
|
||||
DD2E65262767A01F00E45FC5 /* NodeInfoEntityDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeInfoEntityDetail.swift */; };
|
||||
DD47E3CC26F0E51D00029299 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CB26F0E51D00029299 /* NodeDetail.swift */; };
|
||||
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; };
|
||||
DD47E3D026F1073F00029299 /* NodeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CF26F1073F00029299 /* NodeRow.swift */; };
|
||||
|
|
@ -19,13 +21,13 @@
|
|||
DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */; };
|
||||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; };
|
||||
DD4A91202708C66600501B7E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911F2708C66600501B7E /* Configuration.swift */; };
|
||||
DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; };
|
||||
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; };
|
||||
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; };
|
||||
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; };
|
||||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AE626F6B38600ABCC23 /* Connect.swift */; };
|
||||
DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEC26F858F900ABCC23 /* MeshData.swift */; };
|
||||
DD836AEF26F85D8D00ABCC23 /* NodeInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEE26F85D8D00ABCC23 /* NodeInfoModel.swift */; };
|
||||
DD8EDE9426F97A2B00A5A10B /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD8EDE9326F97A2B00A5A10B /* SwiftProtobuf */; };
|
||||
DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860B26F684AF00DC5189 /* BatteryIcon.swift */; };
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
|
||||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
|
||||
|
|
@ -73,6 +75,8 @@
|
|||
/* Begin PBXFileReference section */
|
||||
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
|
||||
DD23A51226FEF5D500D9B90C /* MessageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageData.swift; sourceTree = "<group>"; };
|
||||
DD2E652327679E4000E45FC5 /* NodeInfoEntityRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityRow.swift; sourceTree = "<group>"; };
|
||||
DD2E65252767A01F00E45FC5 /* NodeInfoEntityDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityDetail.swift; sourceTree = "<group>"; };
|
||||
DD47E3CB26F0E51D00029299 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = "<group>"; };
|
||||
DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = "<group>"; };
|
||||
DD47E3CF26F1073F00029299 /* NodeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeRow.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -128,7 +132,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DD8EDE9426F97A2B00A5A10B /* SwiftProtobuf in Frameworks */,
|
||||
DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -156,6 +160,8 @@
|
|||
DD47E3CD26F103C600029299 /* NodeList.swift */,
|
||||
DD47E3CF26F1073F00029299 /* NodeRow.swift */,
|
||||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
|
||||
DD2E652327679E4000E45FC5 /* NodeInfoEntityRow.swift */,
|
||||
DD2E65252767A01F00E45FC5 /* NodeInfoEntityDetail.swift */,
|
||||
);
|
||||
path = Nodes;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -360,7 +366,7 @@
|
|||
);
|
||||
name = MeshtasticClient;
|
||||
packageProductDependencies = (
|
||||
DD8EDE9326F97A2B00A5A10B /* SwiftProtobuf */,
|
||||
DD5394FB276993AD00AD86B1 /* SwiftProtobuf */,
|
||||
);
|
||||
productName = MeshtasticClient;
|
||||
productReference = DDC2E15426CE248E0042C5E4 /* MeshtasticClient.app */;
|
||||
|
|
@ -434,7 +440,7 @@
|
|||
);
|
||||
mainGroup = DDC2E14B26CE248E0042C5E4;
|
||||
packageReferences = (
|
||||
DDAF8C5926ED08D30058C060 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||
DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||
);
|
||||
productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -507,6 +513,7 @@
|
|||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */,
|
||||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
|
||||
DDC4D568275499A500A4208E /* Persistence.swift in Sources */,
|
||||
DD2E652427679E4000E45FC5 /* NodeInfoEntityRow.swift in Sources */,
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
|
||||
DD47E3DB26F3901B00029299 /* Channels.swift in Sources */,
|
||||
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
|
||||
|
|
@ -531,6 +538,7 @@
|
|||
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
|
||||
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,
|
||||
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
|
||||
DD2E65262767A01F00E45FC5 /* NodeInfoEntityDetail.swift in Sources */,
|
||||
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
|
||||
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
|
||||
DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */,
|
||||
|
|
@ -870,20 +878,20 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
DDAF8C5926ED08D30058C060 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
|
||||
DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/apple/swift-protobuf.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.17.0;
|
||||
minimumVersion = 1.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
DD8EDE9326F97A2B00A5A10B /* SwiftProtobuf */ = {
|
||||
DD5394FB276993AD00AD86B1 /* SwiftProtobuf */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DDAF8C5926ED08D30058C060 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
||||
package = DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
||||
productName = SwiftProtobuf;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
|
|
|||
|
|
@ -65,18 +65,82 @@
|
|||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "C16C366D-F4CF-4FE2-A87D-3A8BBA3A2840"
|
||||
uuid = "2CAE774E-7819-413A-91D3-559FCC82C1FB"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "MeshtasticClient/Helpers/BLEManager.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "555"
|
||||
endingLineNumber = "555"
|
||||
landmarkName = "peripheral(_:didUpdateValueFor:error:)"
|
||||
startingLineNumber = "839"
|
||||
endingLineNumber = "839"
|
||||
landmarkName = "sendMessage(message:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "BE4B1DBA-2314-49AB-B64E-4DB2EBF1A1C7"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "MeshtasticClient/Helpers/BLEManager.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "862"
|
||||
endingLineNumber = "862"
|
||||
landmarkName = "sendMessage(message:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "2CF5DF91-B5B3-4CAF-8A13-C6B6815835CE"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "MeshtasticClient/Views/Helpers/MessageBubble.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "38"
|
||||
endingLineNumber = "38"
|
||||
landmarkName = "body"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "03628B8D-EBF0-453E-884C-1161447647CD"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "MeshtasticClient/Views/Messages/Messages.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "54"
|
||||
endingLineNumber = "54"
|
||||
landmarkName = "body"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "2F7DD194-C7B0-472F-8346-9128AE44D6AD"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "MeshtasticClient/Views/Messages/Messages.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "56"
|
||||
endingLineNumber = "56"
|
||||
landmarkName = "body"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
|
||||
var context: NSManagedObjectContext?
|
||||
|
||||
private var centralManager: CBCentralManager!
|
||||
|
||||
@Published var peripherals = [Peripheral]()
|
||||
|
||||
@Published var connectedPeripheral: Peripheral!
|
||||
@Published var connectedNode: NodeInfoModel!
|
||||
@Published var connectedNode: NodeInfoEntity!
|
||||
@Published var lastConnectedPeripheral: String
|
||||
@Published var lastConnectionError: String
|
||||
|
||||
|
|
@ -49,25 +50,16 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
private var meshLoggingEnabled: Bool = false
|
||||
let meshLog = documentsFolder.appendingPathComponent("meshlog.txt")
|
||||
|
||||
|
||||
//Eventually Delete
|
||||
@Published var meshData: MeshData
|
||||
//@Published var messageData: MessageData
|
||||
|
||||
// MARK: init BLEManager
|
||||
override init() {
|
||||
|
||||
self.meshLoggingEnabled = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false
|
||||
self.meshData = MeshData()
|
||||
//self.messageData = MessageData()
|
||||
self.lastConnectedPeripheral = ""
|
||||
self.lastConnectionError = ""
|
||||
super.init()
|
||||
//let bleQueue: DispatchQueue = DispatchQueue(label: "CentralManager")
|
||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
||||
meshData.load()
|
||||
//messageData.load()
|
||||
}
|
||||
|
||||
// MARK: Bluetooth enabled/disabled for the app
|
||||
|
|
@ -82,7 +74,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Scanning for BLE Devices
|
||||
// Scan for nearby BLE devices using the Meshtastic BLE service ID
|
||||
func startScanning() {
|
||||
|
|
@ -113,8 +104,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
/// - timer: The time that fired the event
|
||||
///
|
||||
@objc func timeoutTimerFired(timer: Timer) {
|
||||
guard let context = timer.userInfo as? [String: String] else { return }
|
||||
let name: String = context["name", default: "Unknown"]
|
||||
guard let timerContext = timer.userInfo as? [String: String] else { return }
|
||||
let name: String = timerContext["name", default: "Unknown"]
|
||||
|
||||
self.timeoutTimerCount += 1
|
||||
|
||||
|
|
@ -124,7 +115,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
|
||||
}
|
||||
connectedNode = nil
|
||||
connectedPeripheral = nil
|
||||
|
||||
self.lastConnectionError = "BLE Connecting Timeout after making \(timeoutTimerCount) attempts to connect to \(name)."
|
||||
|
|
@ -177,12 +167,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
peripheralName = name
|
||||
}
|
||||
|
||||
var newPeripheral = Peripheral(id: peripheral.identifier.uuidString, name: peripheralName, rssi: RSSI.intValue, subscribed: false, peripheral: peripheral, myInfo: nil)
|
||||
let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, name: peripheralName, firmwareVersion: "Unknown", rssi: RSSI.intValue, subscribed: false, peripheral: peripheral, myInfo: nil)
|
||||
let peripheralIndex = peripherals.firstIndex(where: { $0.id == newPeripheral.id })
|
||||
|
||||
if peripheralIndex != nil && newPeripheral.peripheral.state != CBPeripheralState.connected {
|
||||
|
||||
newPeripheral.myInfo = peripherals.first(where: { $0.id == newPeripheral.id })?.myInfo
|
||||
//newPeripheral.myInfo = peripherals.first(where: { $0.id == newPeripheral.id })?.myInfo
|
||||
peripherals[peripheralIndex!] = newPeripheral
|
||||
peripherals.remove(at: peripheralIndex!)
|
||||
peripherals.append(newPeripheral)
|
||||
|
|
@ -211,9 +201,23 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
// Map the peripheral to the connectedNode and connectedPeripheral ObservedObjects
|
||||
connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first
|
||||
connectedPeripheral.peripheral.delegate = self
|
||||
let deviceName = peripheral.name ?? ""
|
||||
let peripheralLast4: String = String(deviceName.suffix(4))
|
||||
connectedNode = self.meshData.nodes.first(where: { $0.user.id.contains(peripheralLast4) })
|
||||
|
||||
let fetchNodeRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeRequest.predicate = NSPredicate(format: "bleName MATCHES %@", String(peripheral.name ?? "???"))
|
||||
|
||||
do {
|
||||
let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity]
|
||||
|
||||
if fetchedNode.count == 1 {
|
||||
|
||||
connectedPeripheral.name = fetchedNode[0].user!.longName!
|
||||
connectedPeripheral.firmwareVersion = (fetchedNode[0].myInfo?.firmwareVersion ?? "Unknown")
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("Fetch NodeInfo Failed")
|
||||
}
|
||||
|
||||
lastConnectedPeripheral = peripheral.identifier.uuidString
|
||||
|
||||
// Discover Services
|
||||
|
|
@ -257,19 +261,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
// Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work.
|
||||
lastConnectionError = e.localizedDescription
|
||||
self.connectedNode = nil
|
||||
|
||||
print("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") }
|
||||
} else if errorCode == 14 { // Peer removed pairing information
|
||||
|
||||
// Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that
|
||||
lastConnectionError = "\(e.localizedDescription) This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."
|
||||
self.connectedNode = nil
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") }
|
||||
} else {
|
||||
|
||||
lastConnectionError = e.localizedDescription
|
||||
self.connectedNode = nil
|
||||
|
||||
print("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") }
|
||||
}
|
||||
|
|
@ -277,7 +281,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
// Disconnected without error which indicates user intent to disconnect
|
||||
// Happens when swiping to disconnect
|
||||
self.connectedNode = nil
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect") }
|
||||
print("BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect")
|
||||
}
|
||||
|
|
@ -394,7 +397,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
let fetchMyInfoRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %i", Int64(decodedInfo.myInfo.myNodeNum))
|
||||
|
||||
|
||||
do {
|
||||
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
// Not Found Insert
|
||||
|
|
@ -407,28 +409,36 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
myInfo.messageTimeoutMsec = Int32(bitPattern: decodedInfo.myInfo.messageTimeoutMsec)
|
||||
myInfo.minAppVersion = Int32(bitPattern: decodedInfo.myInfo.minAppVersion)
|
||||
myInfo.maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels)
|
||||
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
print("Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)")
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("Error Saving CoreData MyInfoEntity: \(nsError)")
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
fetchedMyInfo[0].myNodeNum = Int64(decodedInfo.myInfo.myNodeNum)
|
||||
fetchedMyInfo[0].hasGps = decodedInfo.myInfo.hasGps_p
|
||||
fetchedMyInfo[0].numBands = Int32(bitPattern: decodedInfo.myInfo.numBands)
|
||||
fetchedMyInfo[0].firmwareVersion = decodedInfo.myInfo.firmwareVersion
|
||||
fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: decodedInfo.myInfo.messageTimeoutMsec)
|
||||
fetchedMyInfo[0].minAppVersion = Int32(bitPattern: decodedInfo.myInfo.minAppVersion)
|
||||
fetchedMyInfo[0].maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels)
|
||||
}
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
print("Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)")
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? String(decodedInfo.myInfo.myNodeNum))") }
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("Error Saving CoreData MyInfoEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("Fetch MyInfo Error")
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? String(myInfo.myNodeNum))") }
|
||||
}
|
||||
|
||||
// NodeInfo Data
|
||||
|
|
@ -437,9 +447,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
print("Save a CoreData NodeInfoEntity")
|
||||
|
||||
let fetchNodeRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeRequest.predicate = NSPredicate(format: "num == %i", Int64(decodedInfo.nodeInfo.num))
|
||||
fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity]
|
||||
// Not Found Insert
|
||||
if fetchedNode.isEmpty {
|
||||
|
|
@ -466,19 +477,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
newNode.user = newUser
|
||||
}
|
||||
|
||||
if decodedInfo.nodeInfo.hasPosition && decodedInfo.nodeInfo.position.latitudeI != 0 {
|
||||
|
||||
let position = PositionEntity(context: context!)
|
||||
position.latitudeI = decodedInfo.nodeInfo.position.latitudeI
|
||||
position.longitudeI = decodedInfo.nodeInfo.position.longitudeI
|
||||
position.altitude = decodedInfo.nodeInfo.position.altitude
|
||||
position.batteryLevel = decodedInfo.nodeInfo.position.batteryLevel
|
||||
position.time = Int32(bitPattern: decodedInfo.nodeInfo.position.time)
|
||||
|
||||
var newPostions = [PositionEntity]()
|
||||
newPostions.append(position)
|
||||
newNode.positions? = NSOrderedSet(array : newPostions)
|
||||
}
|
||||
// if false && decodedInfo.nodeInfo.hasPosition && decodedInfo.nodeInfo.position.latitudeI != 0 {
|
||||
//
|
||||
// let position = PositionEntity(context: context!)
|
||||
// position.latitudeI = decodedInfo.nodeInfo.position.latitudeI
|
||||
// position.longitudeI = decodedInfo.nodeInfo.position.longitudeI
|
||||
// position.altitude = decodedInfo.nodeInfo.position.altitude
|
||||
// position.batteryLevel = decodedInfo.nodeInfo.position.batteryLevel
|
||||
// position.time = Int32(bitPattern: decodedInfo.nodeInfo.position.time)
|
||||
//
|
||||
// var newPostions = [PositionEntity]()
|
||||
// newPostions.append(position)
|
||||
// newNode.positions? = NSOrderedSet(array : newPostions)
|
||||
// }
|
||||
|
||||
// Look for a MyInfo
|
||||
let fetchMyInfoRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
|
|
@ -498,44 +509,44 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
} else {
|
||||
|
||||
let updatedNode = fetchedNode[0]
|
||||
updatedNode.timestamp = Date()
|
||||
updatedNode.lastHeard = Int32(bitPattern: decodedInfo.nodeInfo.lastHeard)
|
||||
updatedNode.snr = decodedInfo.nodeInfo.snr
|
||||
fetchedNode[0].id = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].num = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].timestamp = Date()
|
||||
fetchedNode[0].lastHeard = Int32(bitPattern: decodedInfo.nodeInfo.lastHeard)
|
||||
fetchedNode[0].snr = decodedInfo.nodeInfo.snr
|
||||
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
updatedNode.user!.userId = decodedInfo.nodeInfo.user.id
|
||||
updatedNode.user!.longName = decodedInfo.nodeInfo.user.longName
|
||||
updatedNode.user!.shortName = decodedInfo.nodeInfo.user.shortName
|
||||
updatedNode.user!.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased()
|
||||
}
|
||||
if decodedInfo.nodeInfo.hasPosition {
|
||||
|
||||
let position = PositionEntity(context: context!)
|
||||
position.latitudeI = decodedInfo.nodeInfo.position.latitudeI
|
||||
position.longitudeI = decodedInfo.nodeInfo.position.longitudeI
|
||||
position.altitude = decodedInfo.nodeInfo.position.altitude
|
||||
position.batteryLevel = decodedInfo.nodeInfo.position.batteryLevel
|
||||
position.time = Int32(bitPattern: decodedInfo.nodeInfo.position.time)
|
||||
|
||||
if position.latitudeI != 0 {
|
||||
let mutablePositions = updatedNode.positions!.mutableCopy() as! NSMutableOrderedSet
|
||||
mutablePositions.add(position)
|
||||
updatedNode.positions = mutablePositions.copy() as? NSOrderedSet
|
||||
}
|
||||
fetchedNode[0].user!.userId = decodedInfo.nodeInfo.user.id
|
||||
fetchedNode[0].user!.longName = decodedInfo.nodeInfo.user.longName
|
||||
fetchedNode[0].user!.shortName = decodedInfo.nodeInfo.user.shortName
|
||||
fetchedNode[0].user!.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased()
|
||||
}
|
||||
// if decodedInfo.nodeInfo.hasPosition {
|
||||
//
|
||||
// let position = PositionEntity(context: context!)
|
||||
// position.latitudeI = decodedInfo.nodeInfo.position.latitudeI
|
||||
// position.longitudeI = decodedInfo.nodeInfo.position.longitudeI
|
||||
// position.altitude = decodedInfo.nodeInfo.position.altitude
|
||||
// position.batteryLevel = decodedInfo.nodeInfo.position.batteryLevel
|
||||
// position.time = Int32(bitPattern: decodedInfo.nodeInfo.position.time)
|
||||
//
|
||||
// if position.latitudeI != 0 {
|
||||
// let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
|
||||
// mutablePositions.add(position)
|
||||
// fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
|
||||
// }
|
||||
// }
|
||||
// Look for a MyInfo
|
||||
let fetchMyInfoRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %i", Int64(decodedInfo.nodeInfo.num))
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
if fetchedMyInfo.count > 0 {
|
||||
|
||||
updatedNode.myInfo = fetchedMyInfo[0]
|
||||
|
||||
fetchedNode[0].myInfo = fetchedMyInfo[0]
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
|
@ -570,62 +581,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
print("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.num)")
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.num)") }
|
||||
}
|
||||
|
||||
// if meshData.nodes.contains(where: {$0.id == decodedInfo.nodeInfo.num}) {
|
||||
//
|
||||
// // Found a matching node lets update it
|
||||
// let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num })
|
||||
// if connectedPeripheral != nil && connectedPeripheral.myInfo?.myNodeNum == nodeMatch?.num {
|
||||
// connectedNode = nodeMatch
|
||||
// }
|
||||
//
|
||||
// if nodeMatch?.lastHeard ?? 0 < decodedInfo.nodeInfo.lastHeard && nodeMatch?.user != nil && nodeMatch?.user.longName.count ?? 0 > 0 {
|
||||
// // The data coming from the device is newer
|
||||
//
|
||||
// let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.nodeInfo.num })
|
||||
// meshData.nodes.remove(at: nodeIndex!)
|
||||
// meshData.save()
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// // Data is older than what the app already has
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// Set the connected node if the nodeInfo is for the connected node.
|
||||
// if connectedPeripheral != nil && connectedPeripheral.myInfo?.myNodeNum == decodedInfo.nodeInfo.num {
|
||||
//
|
||||
// let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num })
|
||||
// if nodeMatch != nil {
|
||||
// connectedNode = nodeMatch
|
||||
// }
|
||||
// }
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
meshData.nodes.append(
|
||||
NodeInfoModel(
|
||||
num: decodedInfo.nodeInfo.num,
|
||||
user: NodeInfoModel.User(
|
||||
id: decodedInfo.nodeInfo.user.id,
|
||||
longName: decodedInfo.nodeInfo.user.longName,
|
||||
shortName: decodedInfo.nodeInfo.user.shortName,
|
||||
// macaddr: decodedInfo.nodeInfo.user.macaddr,
|
||||
hwModel: String(describing: decodedInfo.nodeInfo.user.hwModel)
|
||||
.uppercased()),
|
||||
|
||||
position: NodeInfoModel.Position(
|
||||
latitudeI: decodedInfo.nodeInfo.position.latitudeI,
|
||||
longitudeI: decodedInfo.nodeInfo.position.longitudeI,
|
||||
altitude: decodedInfo.nodeInfo.position.altitude,
|
||||
batteryLevel: decodedInfo.nodeInfo.position.batteryLevel,
|
||||
time: decodedInfo.nodeInfo.position.time),
|
||||
|
||||
lastHeard: decodedInfo.nodeInfo.lastHeard,
|
||||
snr: decodedInfo.nodeInfo.snr)
|
||||
)
|
||||
meshData.save()
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") }
|
||||
}
|
||||
}
|
||||
// Handle assorted app packets
|
||||
if decodedInfo.packet.id != 0 {
|
||||
|
|
@ -650,12 +605,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
let fetchedUsers = try context?.fetch(messageUsers) as! [UserEntity]
|
||||
|
||||
let newMessage = MessageEntity(context: context!)
|
||||
newMessage.messageId = Int32(bitPattern: decodedInfo.packet.id)
|
||||
newMessage.messageId = Int64(decodedInfo.packet.id)
|
||||
newMessage.messageTimestamp = Int32(bitPattern: decodedInfo.packet.rxTime)
|
||||
newMessage.receivedACK = false
|
||||
newMessage.direction = "IN"
|
||||
|
||||
if decodedInfo.packet.to == broadcastNodeId {
|
||||
if decodedInfo.packet.to == broadcastNodeId && fetchedUsers.count == 1 {
|
||||
|
||||
let bcu: UserEntity = UserEntity(context: context!)
|
||||
bcu.shortName = "BC"
|
||||
|
|
@ -693,11 +648,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
} catch {
|
||||
|
||||
print("Something went wrong: \(error)")
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("Unresolved error \(nsError)")
|
||||
print("Failed to save new MessageEntity \(nsError)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
|
@ -706,41 +660,103 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
}
|
||||
} else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp {
|
||||
|
||||
//let fetchNodeInfoAppRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
//fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %i", Int64(decodedInfo.packet.from))
|
||||
|
||||
let fetchNodeRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.packet.from))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity]
|
||||
|
||||
var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from })
|
||||
if updatedNode != nil {
|
||||
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received for node info app for \(updatedNode!.user.longName)") }
|
||||
}
|
||||
|
||||
if updatedNode != nil {
|
||||
updatedNode!.snr = decodedInfo.packet.rxSnr
|
||||
updatedNode!.lastHeard = decodedInfo.packet.rxTime
|
||||
// updatedNode!.update(from: updatedNode!.data)
|
||||
let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from })
|
||||
meshData.nodes.remove(at: nodeIndex!)
|
||||
meshData.nodes.append(updatedNode!)
|
||||
meshData.save()
|
||||
if meshLoggingEnabled { MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Node Info App Packet For: \(updatedNode!.user.longName)") }
|
||||
print("Updated NodeInfo SNR and Time from Packet For: \(updatedNode!.user.longName)")
|
||||
if fetchedNode.count == 1 {
|
||||
fetchedNode[0].id = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].num = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].timestamp = Date()
|
||||
fetchedNode[0].lastHeard = Int32(bitPattern: decodedInfo.packet.rxTime)
|
||||
fetchedNode[0].snr = decodedInfo.packet.rxSnr
|
||||
}
|
||||
else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
|
||||
if meshLoggingEnabled {
|
||||
MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Node Info App Packet For: \(fetchedNode[0].num)")
|
||||
}
|
||||
print("Updated NodeInfo SNR and Time from Packet For: \(fetchedNode[0].num)")
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)")
|
||||
}
|
||||
} catch {
|
||||
|
||||
print("Error Fetching NodeInfoEntity for NODEINFO_APP")
|
||||
}
|
||||
|
||||
|
||||
print(decodedInfo.packet.decoded.payload)
|
||||
|
||||
|
||||
|
||||
} else if decodedInfo.packet.decoded.portnum == PortNum.positionApp {
|
||||
|
||||
var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from })
|
||||
let fetchNodePositionRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.packet.from))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedNode = try context?.fetch(fetchNodePositionRequest) as! [NodeInfoEntity]
|
||||
|
||||
if updatedNode != nil {
|
||||
updatedNode!.snr = decodedInfo.packet.rxSnr
|
||||
updatedNode!.lastHeard = decodedInfo.packet.rxTime
|
||||
// updatedNode!.update(from: updatedNode!.data)
|
||||
let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from })
|
||||
meshData.nodes.remove(at: nodeIndex!)
|
||||
meshData.nodes.append(updatedNode!)
|
||||
meshData.save()
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Position App Packet For: \(updatedNode!.user.longName)") }
|
||||
print("Updated Position SNR and Time from Packet For: \(updatedNode!.user.longName)")
|
||||
if fetchedNode.count == 1 {
|
||||
fetchedNode[0].id = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].num = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].timestamp = Date()
|
||||
fetchedNode[0].lastHeard = Int32(bitPattern: decodedInfo.packet.rxTime)
|
||||
fetchedNode[0].snr = decodedInfo.packet.rxSnr
|
||||
}
|
||||
else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
|
||||
if meshLoggingEnabled {
|
||||
MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Node Info App Packet For: \(fetchedNode[0].num)")
|
||||
}
|
||||
print("Updated NodeInfo SNR and Time from Packet For: \(fetchedNode[0].num)")
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)")
|
||||
}
|
||||
} catch {
|
||||
|
||||
print("Error Fetching NodeInfoEntity for NODEINFO_APP")
|
||||
}
|
||||
print("Postion Payload")
|
||||
print(try decodedInfo.packet.jsonString())
|
||||
|
||||
|
||||
print(decodedInfo.packet.decoded.payload)
|
||||
// if meshLoggingEnabled {
|
||||
// MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Position App Packet For: \(updatedNode.num)")
|
||||
// }
|
||||
// print("Updated NodeInfo SNR and Time from Packet For: \(updatedNode.num)")
|
||||
//
|
||||
// print("Postion Payload")
|
||||
// print(try decodedInfo.packet.jsonString())
|
||||
|
||||
} else if decodedInfo.packet.decoded.portnum == PortNum.adminApp {
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("MESH PACKET received for Admin App UNHANDLED \(try decodedInfo.packet.jsonString())") }
|
||||
|
|
@ -804,17 +820,25 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
success = false
|
||||
} else {
|
||||
|
||||
var longName: String = self.connectedPeripheral.name
|
||||
var shortName: String = "???"
|
||||
var nodeNum: UInt32 = self.connectedPeripheral.myInfo?.myNodeNum ?? 0
|
||||
let newMessage = MessageEntity(context: context!)
|
||||
newMessage.messageId = Int64(bitPattern: 0)
|
||||
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
newMessage.receivedACK = false
|
||||
newMessage.direction = "IN"
|
||||
|
||||
|
||||
if connectedNode != nil {
|
||||
longName = connectedNode.user.longName
|
||||
shortName = connectedNode.user.shortName
|
||||
nodeNum = connectedNode.num
|
||||
}
|
||||
let bcu: UserEntity = UserEntity(context: context!)
|
||||
bcu.shortName = "BC"
|
||||
bcu.longName = "Broadcast"
|
||||
bcu.hwModel = "UNSET"
|
||||
bcu.num = Int64(broadcastNodeId)
|
||||
bcu.userId = "BROADCASTNODE"
|
||||
newMessage.toUser = bcu
|
||||
|
||||
// Set from user from query here
|
||||
|
||||
let messageModel = MessageModel(messageId: 0, messageTimeStamp: UInt32(Date().timeIntervalSince1970), fromUserId: nodeNum, toUserId: broadcastNodeId, fromUserLongName: longName, toUserLongName: "Broadcast", fromUserShortName: shortName, toUserShortName: "BC", receivedACK: false, messagePayload: message, direction: "OUT")
|
||||
newMessage.messagePayload = message
|
||||
|
||||
let dataType = PortNum.textMessageApp
|
||||
let payloadData: Data = message.data(using: String.Encoding.utf8)!
|
||||
|
||||
|
|
@ -834,9 +858,20 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
//messageData.messages.append(messageModel)
|
||||
//messageData.save()
|
||||
success = true
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
print("Saved a new message for \(0)")
|
||||
success = true
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("Unresolved error \(nsError)")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return success
|
||||
|
|
|
|||
|
|
@ -21,6 +21,15 @@ struct MeshtasticClientApp: App {
|
|||
.onChange(of: scenePhase) { (newScenePhase) in
|
||||
switch newScenePhase {
|
||||
case .background:
|
||||
do {
|
||||
|
||||
try persistenceController.container.viewContext.save()
|
||||
print("Saved viewContext when the app went to the background.")
|
||||
|
||||
} catch {
|
||||
|
||||
print("Failed to save viewContext when the app goes to the background.")
|
||||
}
|
||||
print("Scene is in the background")
|
||||
case .inactive:
|
||||
print("Scene is inactive")
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@ import CoreBluetooth
|
|||
struct Peripheral: Identifiable {
|
||||
var id: String
|
||||
var name: String
|
||||
var firmwareVersion: String
|
||||
var rssi: Int
|
||||
var subscribed: Bool
|
||||
var peripheral: CBPeripheral
|
||||
|
||||
var myInfo: MyInfoModel?
|
||||
|
||||
init(id: String, name: String, rssi: Int, subscribed: Bool, peripheral: CBPeripheral, myInfo: MyInfoModel?) {
|
||||
init(id: String, name: String, firmwareVersion: String, rssi: Int, subscribed: Bool, peripheral: CBPeripheral, myInfo: MyInfoModel?) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.firmwareVersion = firmwareVersion
|
||||
self.rssi = rssi
|
||||
self.subscribed = subscribed
|
||||
self.peripheral = peripheral
|
||||
|
|
|
|||
|
|
@ -50,15 +50,15 @@ struct Connect: View {
|
|||
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
if bleManager.connectedNode != nil {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
|
||||
Text(bleManager.connectedNode.user.longName).font(.title2)
|
||||
Text(bleManager.connectedPeripheral.name).font(.title2)
|
||||
} else {
|
||||
|
||||
Text(String(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")).font(.title2)
|
||||
}
|
||||
if bleManager.connectedNode != nil {
|
||||
Text("Model: ").font(.caption)+Text(bleManager.connectedNode?.user.hwModel ?? "(null)").font(.caption).foregroundColor(Color.gray)
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
//Text("Model: ").font(.caption)+Text(bleManager.connectedNode?.user!.hwModel ?? "(null)").font(.caption).foregroundColor(Color.gray)
|
||||
}
|
||||
Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.name).font(.caption).foregroundColor(Color.gray)
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
|
|
@ -80,8 +80,8 @@ struct Connect: View {
|
|||
.onChange(of: isPreferredRadio) { value in
|
||||
if value {
|
||||
|
||||
if bleManager.connectedNode != nil {
|
||||
let deviceName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")"
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "")
|
||||
|
||||
userSettings.preferredPeripheralName = deviceName
|
||||
} else {
|
||||
|
|
@ -201,7 +201,7 @@ struct Connect: View {
|
|||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
//ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,6 @@ struct ContentView: View {
|
|||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
.environmentObject(MeshData())
|
||||
// .environmentObject(MeshData())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,26 +10,23 @@ struct Channels: View {
|
|||
|
||||
NavigationView {
|
||||
|
||||
GeometryReader { _ in
|
||||
NavigationLink(destination: Messages(), isActive: $isShowingDetailView) {
|
||||
|
||||
NavigationLink(destination: Messages(), isActive: $isShowingDetailView) {
|
||||
List {
|
||||
|
||||
List {
|
||||
HStack {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "dial.max.fill")
|
||||
.font(.system(size: 62))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.padding(.trailing)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
Image(systemName: "dial.max.fill")
|
||||
.font(.system(size: 62))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.padding(.trailing)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Primary")
|
||||
.font(.largeTitle)
|
||||
|
||||
Text("Primary")
|
||||
.font(.largeTitle)
|
||||
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Channels")
|
||||
}
|
||||
|
|
@ -39,8 +36,6 @@ struct Channels: View {
|
|||
|
||||
struct MessageList_Previews: PreviewProvider {
|
||||
|
||||
static let meshData = MeshData()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
Channels()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ struct Messages: View {
|
|||
@FocusState private var focusedField: Field?
|
||||
|
||||
@State var showDeleteMessageAlert = false
|
||||
@State private var deleteMessageId: Int32 = 0
|
||||
@State private var deleteMessageId: Int64 = 0
|
||||
|
||||
public var broadcastNodeId: UInt32 = 4294967295
|
||||
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
|
|
@ -48,15 +48,16 @@ struct Messages: View {
|
|||
ForEach(messages) { message in
|
||||
|
||||
HStack(alignment: .top) {
|
||||
let currentUser: Bool = false//(bleManager.connectedNode != nil) && ((bleManager.connectedNode.id) == message.fromUser!.num)
|
||||
let currentUser: Bool = false// message.fromUser != nil && ((bleManager.connectedPeripheral) == message.fromUser!.num)
|
||||
|
||||
|
||||
CircleText(text: "???", color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
|
||||
CircleText(text: (message.fromUser?.longName ?? "???"), color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
|
||||
.gesture(LongPressGesture(minimumDuration: 2)
|
||||
.onEnded {_ in
|
||||
print("I want to delete message: \(message.messageId)")
|
||||
self.showDeleteMessageAlert = true
|
||||
self.deleteMessageId = message.messageId
|
||||
|
||||
print(deleteMessageId)
|
||||
})
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
|
|
@ -193,16 +194,17 @@ struct Messages: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("Channel - Primary")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
//.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
//ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
|
||||
})
|
||||
.onAppear {
|
||||
.onAppear(perform: {
|
||||
|
||||
self.bleManager.context = context
|
||||
messageCount = messages.count
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,176 +1,176 @@
|
|||
/*
|
||||
Abstract:
|
||||
A view showing the details for a node.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct NodeDetail: View {
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
var node: NodeInfoModel
|
||||
|
||||
struct MapLocation: Identifiable {
|
||||
let id = UUID()
|
||||
let name: String
|
||||
let coordinate: CLLocationCoordinate2D
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
GeometryReader { bounds in
|
||||
|
||||
VStack {
|
||||
|
||||
if node.position.coordinate != nil {
|
||||
|
||||
let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: node.position.latitude!, longitude: node.position.longitude!)
|
||||
|
||||
let regionBinding = Binding<MKCoordinateRegion>(
|
||||
get: {
|
||||
MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
|
||||
},
|
||||
set: { _ in }
|
||||
)
|
||||
let annotations = [MapLocation(name: node.user.shortName, coordinate: node.position.coordinate!)]
|
||||
|
||||
Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in
|
||||
MapAnnotation(
|
||||
coordinate: location.coordinate,
|
||||
content: {
|
||||
CircleText(text: node.user.shortName, color: .accentColor)
|
||||
}
|
||||
)
|
||||
}.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
|
||||
} else {
|
||||
Image(node.user.hwModel)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
}
|
||||
ScrollView {
|
||||
|
||||
HStack {
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("AKA").font(.title2).fixedSize()
|
||||
CircleText(text: node.user.shortName, color: .accentColor)
|
||||
.offset(y: 10)
|
||||
}
|
||||
.padding([.leading, .trailing, .bottom])
|
||||
Divider()
|
||||
if node.snr != nil && node.snr! > 0 {
|
||||
VStack(alignment: .center) {
|
||||
|
||||
Image(systemName: "waveform.path")
|
||||
.font(.title)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("SNR").font(.title2).fixedSize()
|
||||
Text(String(node.snr ?? 0))
|
||||
.font(.title2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
VStack(alignment: .center) {
|
||||
BatteryIcon(batteryLevel: node.position.batteryLevel, font: .title, color: .accentColor)
|
||||
if node.position.batteryLevel != nil && node.position.batteryLevel! > 0 {
|
||||
Text("Battery").font(.title2).fixedSize()
|
||||
Text(String(node.position.batteryLevel!) + "%")
|
||||
.font(.title2)
|
||||
.foregroundColor(.gray)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
} else {
|
||||
Text("Powered").font(.title2)
|
||||
}
|
||||
}
|
||||
|
||||
}.padding(4)
|
||||
Divider()
|
||||
HStack {
|
||||
|
||||
Image(node.user.hwModel)
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(5)
|
||||
|
||||
Text("Model: " + String(node.user.hwModel))
|
||||
.font(.title3)
|
||||
}
|
||||
.padding()
|
||||
Divider()
|
||||
|
||||
if node.lastHeard > 0 {
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "clock").font(.title2).foregroundColor(.accentColor)
|
||||
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
Text("Last Heard: \(lastHeard, style: .relative) ago").font(.title3)
|
||||
}.padding()
|
||||
Divider()
|
||||
}
|
||||
|
||||
if node.position.coordinate != nil {
|
||||
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
|
||||
Image(systemName: "mappin").font(.title).foregroundColor(.accentColor)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Latitude").font(.headline)
|
||||
Text(String(node.position.latitude ?? 0)).font(.caption).foregroundColor(.gray)
|
||||
}
|
||||
Divider()
|
||||
VStack(alignment: .leading) {
|
||||
Text("Longitude").font(.headline)
|
||||
Text(String(node.position.longitude ?? 0)).font(.caption).foregroundColor(.gray)
|
||||
}
|
||||
Divider()
|
||||
VStack(alignment: .leading) {
|
||||
Text("Altitude").font(.headline)
|
||||
Text(String(node.position.altitude ?? 0) + " m").font(.caption).foregroundColor(.gray)
|
||||
}
|
||||
}.padding()
|
||||
Divider()
|
||||
}
|
||||
HStack(alignment: .center) {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "person").font(.title3).foregroundColor(.accentColor)
|
||||
Text("Unique Id:").font(.title3)
|
||||
}
|
||||
Text(node.user.id).font(.headline).foregroundColor(.gray)
|
||||
}
|
||||
Divider()
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "number").font(.title3).foregroundColor(.accentColor)
|
||||
Text("Node Number:").font(.title3)
|
||||
}
|
||||
Text(String(node.num)).font(.headline).foregroundColor(.gray)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}.navigationTitle(node.user.longName)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
// ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
})
|
||||
}.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeDetail_Previews: PreviewProvider {
|
||||
static let bleManager = BLEManager()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NodeDetail(node: bleManager.meshData.nodes[0])
|
||||
NodeDetail(node: bleManager.meshData.nodes[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
///*
|
||||
//Abstract:
|
||||
//A view showing the details for a node.
|
||||
//*/
|
||||
//
|
||||
//import SwiftUI
|
||||
//import MapKit
|
||||
//import CoreLocation
|
||||
//
|
||||
//struct NodeDetail: View {
|
||||
//
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
//
|
||||
// var node: NodeInfoModel
|
||||
//
|
||||
// struct MapLocation: Identifiable {
|
||||
// let id = UUID()
|
||||
// let name: String
|
||||
// let coordinate: CLLocationCoordinate2D
|
||||
// }
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// GeometryReader { bounds in
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// if node.position.coordinate != nil {
|
||||
//
|
||||
// let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: node.position.latitude!, longitude: node.position.longitude!)
|
||||
//
|
||||
// let regionBinding = Binding<MKCoordinateRegion>(
|
||||
// get: {
|
||||
// MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
|
||||
// },
|
||||
// set: { _ in }
|
||||
// )
|
||||
// let annotations = [MapLocation(name: node.user.shortName, coordinate: node.position.coordinate!)]
|
||||
//
|
||||
// Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in
|
||||
// MapAnnotation(
|
||||
// coordinate: location.coordinate,
|
||||
// content: {
|
||||
// CircleText(text: node.user.shortName, color: .accentColor)
|
||||
// }
|
||||
// )
|
||||
// }.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
|
||||
// } else {
|
||||
// Image(node.user.hwModel)
|
||||
// .resizable()
|
||||
// .aspectRatio(contentMode: .fit)
|
||||
// .frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
// }
|
||||
// ScrollView {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// VStack(alignment: .center) {
|
||||
// Text("AKA").font(.title2).fixedSize()
|
||||
// CircleText(text: node.user.shortName, color: .accentColor)
|
||||
// .offset(y: 10)
|
||||
// }
|
||||
// .padding([.leading, .trailing, .bottom])
|
||||
// Divider()
|
||||
// if node.snr != nil && node.snr! > 0 {
|
||||
// VStack(alignment: .center) {
|
||||
//
|
||||
// Image(systemName: "waveform.path")
|
||||
// .font(.title)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// Text("SNR").font(.title2).fixedSize()
|
||||
// Text(String(node.snr ?? 0))
|
||||
// .font(.title2)
|
||||
// .foregroundColor(.gray)
|
||||
// }
|
||||
// Divider()
|
||||
// }
|
||||
// VStack(alignment: .center) {
|
||||
// BatteryIcon(batteryLevel: node.position.batteryLevel, font: .title, color: .accentColor)
|
||||
// if node.position.batteryLevel != nil && node.position.batteryLevel! > 0 {
|
||||
// Text("Battery").font(.title2).fixedSize()
|
||||
// Text(String(node.position.batteryLevel!) + "%")
|
||||
// .font(.title2)
|
||||
// .foregroundColor(.gray)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// } else {
|
||||
// Text("Powered").font(.title2)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }.padding(4)
|
||||
// Divider()
|
||||
// HStack {
|
||||
//
|
||||
// Image(node.user.hwModel)
|
||||
// .resizable()
|
||||
// .frame(width: 60, height: 60)
|
||||
// .cornerRadius(5)
|
||||
//
|
||||
// Text("Model: " + String(node.user.hwModel))
|
||||
// .font(.title3)
|
||||
// }
|
||||
// .padding()
|
||||
// Divider()
|
||||
//
|
||||
// if node.lastHeard > 0 {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// Image(systemName: "clock").font(.title2).foregroundColor(.accentColor)
|
||||
// let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
// Text("Last Heard: \(lastHeard, style: .relative) ago").font(.title3)
|
||||
// }.padding()
|
||||
// Divider()
|
||||
// }
|
||||
//
|
||||
// if node.position.coordinate != nil {
|
||||
// HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
|
||||
// Image(systemName: "mappin").font(.title).foregroundColor(.accentColor)
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("Latitude").font(.headline)
|
||||
// Text(String(node.position.latitude ?? 0)).font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
// Divider()
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("Longitude").font(.headline)
|
||||
// Text(String(node.position.longitude ?? 0)).font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
// Divider()
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("Altitude").font(.headline)
|
||||
// Text(String(node.position.altitude ?? 0) + " m").font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
// }.padding()
|
||||
// Divider()
|
||||
// }
|
||||
// HStack(alignment: .center) {
|
||||
// VStack {
|
||||
// HStack {
|
||||
// Image(systemName: "person").font(.title3).foregroundColor(.accentColor)
|
||||
// Text("Unique Id:").font(.title3)
|
||||
// }
|
||||
// Text(node.user.id).font(.headline).foregroundColor(.gray)
|
||||
// }
|
||||
// Divider()
|
||||
// VStack {
|
||||
// HStack {
|
||||
// Image(systemName: "number").font(.title3).foregroundColor(.accentColor)
|
||||
// Text("Node Number:").font(.title3)
|
||||
// }
|
||||
// Text(String(node.num)).font(.headline).foregroundColor(.gray)
|
||||
// }
|
||||
// }.padding()
|
||||
// }
|
||||
// }.navigationTitle(node.user.longName)
|
||||
// .navigationBarTitleDisplayMode(.inline)
|
||||
// .navigationBarItems(trailing:
|
||||
//
|
||||
// ZStack {
|
||||
// // ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
// })
|
||||
// }.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct NodeDetail_Previews: PreviewProvider {
|
||||
// static let bleManager = BLEManager()
|
||||
//
|
||||
// static var previews: some View {
|
||||
// Group {
|
||||
// NodeDetail(node: bleManager.meshData.nodes[0])
|
||||
// NodeDetail(node: bleManager.meshData.nodes[1])
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
175
MeshtasticClient/Views/Nodes/NodeInfoEntityDetail.swift
Normal file
175
MeshtasticClient/Views/Nodes/NodeInfoEntityDetail.swift
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
Abstract:
|
||||
A view showing the details for a node.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct NodeInfoEntityDetail: View {
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
var node: NodeInfoEntity
|
||||
|
||||
struct MapLocation: Identifiable {
|
||||
let id = UUID()
|
||||
let name: String
|
||||
let coordinate: CLLocationCoordinate2D
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
GeometryReader { bounds in
|
||||
|
||||
VStack {
|
||||
|
||||
if node.positions!.count > 0 {
|
||||
|
||||
// let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: node.position.latitude!, longitude: node.position.longitude!)
|
||||
//
|
||||
// let regionBinding = Binding<MKCoordinateRegion>(
|
||||
// get: {
|
||||
// MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
|
||||
// },
|
||||
// set: { _ in }
|
||||
// )
|
||||
// let annotations = [MapLocation(name: node.user.shortName, coordinate: node.position.coordinate!)]
|
||||
//
|
||||
// Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in
|
||||
// MapAnnotation(
|
||||
// coordinate: location.coordinate,
|
||||
// content: {
|
||||
// CircleText(text: node.user.shortName, color: .accentColor)
|
||||
// }
|
||||
// )
|
||||
// }.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
|
||||
} else {
|
||||
Image(node.user?.hwModel ?? "UNSET")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
}
|
||||
ScrollView {
|
||||
|
||||
HStack {
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("AKA").font(.title2).fixedSize()
|
||||
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
|
||||
.offset(y: 10)
|
||||
}
|
||||
.padding([.leading, .trailing, .bottom])
|
||||
Divider()
|
||||
if node.snr > 0 {
|
||||
VStack(alignment: .center) {
|
||||
|
||||
Image(systemName: "waveform.path")
|
||||
.font(.title)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("SNR").font(.title2).fixedSize()
|
||||
Text(String(node.snr))
|
||||
.font(.title2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
// VStack(alignment: .center) {
|
||||
// BatteryIcon(batteryLevel: node.position.batteryLevel, font: .title, color: .accentColor)
|
||||
// if node.position.batteryLevel != nil && node.position.batteryLevel! > 0 {
|
||||
// Text("Battery").font(.title2).fixedSize()
|
||||
// Text(String(node.position.batteryLevel!) + "%")
|
||||
// .font(.title2)
|
||||
// .foregroundColor(.gray)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// } else {
|
||||
// Text("Powered").font(.title2)
|
||||
// }
|
||||
// }
|
||||
|
||||
}.padding(4)
|
||||
Divider()
|
||||
HStack {
|
||||
|
||||
Image(node.user!.hwModel ?? "UNSET")
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(5)
|
||||
|
||||
Text("Model: " + String(node.user!.hwModel ?? "UNSET"))
|
||||
.font(.title3)
|
||||
}
|
||||
.padding()
|
||||
Divider()
|
||||
|
||||
if node.lastHeard > 0 {
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "clock").font(.title2).foregroundColor(.accentColor)
|
||||
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
Text("Last Heard: \(lastHeard, style: .relative) ago").font(.title3)
|
||||
}.padding()
|
||||
Divider()
|
||||
}
|
||||
|
||||
// if node.position.coordinate != nil {
|
||||
// HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
|
||||
// Image(systemName: "mappin").font(.title).foregroundColor(.accentColor)
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("Latitude").font(.headline)
|
||||
// Text(String(node.position.latitude ?? 0)).font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
// Divider()
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("Longitude").font(.headline)
|
||||
// Text(String(node.position.longitude ?? 0)).font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
// Divider()
|
||||
// VStack(alignment: .leading) {
|
||||
// Text("Altitude").font(.headline)
|
||||
// Text(String(node.position.altitude ?? 0) + " m").font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
// }.padding()
|
||||
// Divider()
|
||||
// }
|
||||
HStack(alignment: .center) {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "person").font(.title3).foregroundColor(.accentColor)
|
||||
Text("Unique Id:").font(.title3)
|
||||
}
|
||||
Text(node.user?.userId ?? "??????").font(.headline).foregroundColor(.gray)
|
||||
}
|
||||
Divider()
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "number").font(.title3).foregroundColor(.accentColor)
|
||||
Text("Node Number:").font(.title3)
|
||||
}
|
||||
Text(String(node.num)).font(.headline).foregroundColor(.gray)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}.navigationTitle(node.user!.longName ?? "Unknown")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
// ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
|
||||
})
|
||||
}.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeInfoEntityDetail_Previews: PreviewProvider {
|
||||
static let bleManager = BLEManager()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
//NodeInfoEntityDetail(node: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,89 +11,64 @@
|
|||
import SwiftUI
|
||||
|
||||
struct NodeList: View {
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
|
||||
// CoreData
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \NodeInfoEntity.lastHeard, ascending: false)],
|
||||
animation: .default)
|
||||
|
||||
private var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
@State private var selection: String?
|
||||
|
||||
@State private var showLocationOnly = false
|
||||
|
||||
var filteredDevices: [NodeInfoModel] {
|
||||
bleManager.meshData.nodes.filter { node in
|
||||
(!showLocationOnly || node.position.coordinate != nil)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
|
||||
List {
|
||||
|
||||
if bleManager.meshData.nodes.count == 0 {
|
||||
if nodes.count == 0 {
|
||||
Text("Scan for Radios").font(.largeTitle)
|
||||
// .listRowSeparator(.hidden)
|
||||
Text("No LoRa Mesh Nodes Found").font(.title2)
|
||||
// .listRowSeparator(.hidden)
|
||||
Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your phone or tablet.")
|
||||
.font(.body)
|
||||
// .listRowSeparator(.hidden)
|
||||
Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.")
|
||||
// .listRowSeparator(.hidden)
|
||||
Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.")
|
||||
// .listRowSeparator(.hidden)
|
||||
Spacer()
|
||||
// .listRowSeparator(.hidden)
|
||||
} else {
|
||||
Toggle(isOn: $showLocationOnly) {
|
||||
Text("Nodes with location only")
|
||||
}
|
||||
ForEach(filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard })) { node in
|
||||
ForEach( nodes ) { node in
|
||||
let index = nodes.firstIndex(where: { $0.id == node.id })
|
||||
NavigationLink(destination: NodeInfoEntityDetail(node: node), tag: String(index!), selection: $selection) {
|
||||
|
||||
let index = filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard }).firstIndex(where: { $0.id == node.id })
|
||||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.myInfo != nil {
|
||||
|
||||
NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) {
|
||||
|
||||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.myInfo != nil {
|
||||
|
||||
let connected: Bool = (bleManager.connectedPeripheral.myInfo!.myNodeNum == node.id)
|
||||
NodeRow(node: node, connected: connected)
|
||||
} else {
|
||||
NodeRow(node: node, connected: false)
|
||||
}
|
||||
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
let nodeIndex = bleManager.meshData.nodes.firstIndex(where: { $0.num == node.num })
|
||||
bleManager.meshData.nodes.remove(at: nodeIndex!)
|
||||
bleManager.meshData.save()
|
||||
} label: {
|
||||
|
||||
Label("Delete from app", systemImage: "trash")
|
||||
let connected: Bool = (bleManager.connectedPeripheral.myInfo!.myNodeNum == node.id)
|
||||
NodeInfoEntityRow(node: node, connected: connected)
|
||||
} else {
|
||||
NodeInfoEntityRow(node: node, connected: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("All Nodes")
|
||||
.onAppear(
|
||||
perform: {
|
||||
bleManager.meshData.load()
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
if bleManager.meshData.nodes.count > 0 {
|
||||
selection = "0"
|
||||
}
|
||||
.onAppear{
|
||||
|
||||
self.bleManager.context = context
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
if nodes.count > 0 {
|
||||
selection = "0"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
//.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeList_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NodeList()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,23 @@ import CoreLocation
|
|||
|
||||
struct NodeMap: View {
|
||||
|
||||
// CoreData
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \NodeInfoEntity.lastHeard, ascending: false)],
|
||||
animation: .default)
|
||||
|
||||
private var locationNodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
var locationNodes: [NodeInfoModel] {
|
||||
bleManager.meshData.nodes.filter { node in
|
||||
(node.position.coordinate != nil)
|
||||
}
|
||||
}
|
||||
//var locationNodes: [NodeInfoModel]// {
|
||||
//bleManager.meshData.nodes.filter { node in
|
||||
// (node.position.coordinate != nil)
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
struct MapLocation: Identifiable {
|
||||
let id = UUID()
|
||||
let name: String
|
||||
|
|
@ -39,20 +49,20 @@ struct NodeMap: View {
|
|||
NavigationView {
|
||||
|
||||
ZStack {
|
||||
Map(coordinateRegion: regionBinding,
|
||||
interactionModes: [.all],
|
||||
showsUserLocation: true,
|
||||
userTrackingMode: .constant(.follow), annotationItems: locationNodes) { location in
|
||||
|
||||
MapAnnotation(
|
||||
coordinate: location.position.coordinate!,
|
||||
content: {
|
||||
CircleText(text: location.user.shortName, color: .accentColor)
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
// Map(coordinateRegion: regionBinding,
|
||||
// interactionModes: [.all],
|
||||
// showsUserLocation: true,
|
||||
// userTrackingMode: .constant(.follow), annotationItems: locationNodes) { location in
|
||||
//
|
||||
// MapAnnotation(
|
||||
// coordinate: location.position.coordinate!,
|
||||
// content: {
|
||||
// CircleText(text: location.user.shortName, color: .accentColor)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// .frame(maxHeight: .infinity)
|
||||
// .ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
}
|
||||
.navigationTitle("Mesh Map")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
|
|||
|
|
@ -1,64 +1,64 @@
|
|||
import SwiftUI
|
||||
|
||||
struct NodeRow: View {
|
||||
var node: NodeInfoModel
|
||||
var connected: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
HStack {
|
||||
|
||||
CircleText(text: node.user.shortName, color: Color.accentColor).offset(y: 1).padding(.trailing, 5)
|
||||
.offset(x: -15)
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
Text(node.user.longName).font(.headline)
|
||||
.offset(x: -15)
|
||||
} else {
|
||||
Text(node.user.longName).font(.title)
|
||||
.offset(x: -15)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
||||
HStack(alignment: .bottom) {
|
||||
|
||||
Image(systemName: "clock.badge.checkmark.fill").font(.headline).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical)
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
|
||||
if connected {
|
||||
Text("Currently Connected").font(.caption).foregroundColor(Color.accentColor)
|
||||
} else if node.lastHeard > 0 {
|
||||
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
Text("Last Heard: \(lastHeard, style: .relative) ago").font(.caption).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Last Heard: Unknown").font(.caption).foregroundColor(.gray)
|
||||
}
|
||||
|
||||
} else {
|
||||
if connected {
|
||||
Text("Currently Connected").font(.subheadline).foregroundColor(Color.accentColor)
|
||||
} else if node.lastHeard > 0 {
|
||||
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
Text("Last Heard: \(lastHeard, style: .relative) ago").font(.subheadline).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Last Heard: Unknown").font(.subheadline).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.padding([.leading, .top, .bottom])
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeRow_Previews: PreviewProvider {
|
||||
static var nodes = BLEManager().meshData.nodes
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NodeRow(node: nodes[0], connected: true)
|
||||
}
|
||||
.previewLayout(.fixed(width: 300, height: 70))
|
||||
}
|
||||
}
|
||||
//import SwiftUI
|
||||
//
|
||||
//struct NodeRow: View {
|
||||
// var node: NodeInfoModel
|
||||
// var connected: Bool
|
||||
//
|
||||
// var body: some View {
|
||||
// VStack(alignment: .leading) {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// CircleText(text: node.user.shortName, color: Color.accentColor).offset(y: 1).padding(.trailing, 5)
|
||||
// .offset(x: -15)
|
||||
//
|
||||
// if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
// Text(node.user.longName).font(.headline)
|
||||
// .offset(x: -15)
|
||||
// } else {
|
||||
// Text(node.user.longName).font(.title)
|
||||
// .offset(x: -15)
|
||||
// }
|
||||
// }
|
||||
// .padding(.bottom, 10)
|
||||
//
|
||||
// HStack(alignment: .bottom) {
|
||||
//
|
||||
// Image(systemName: "clock.badge.checkmark.fill").font(.headline).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical)
|
||||
//
|
||||
// if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
//
|
||||
// if connected {
|
||||
// Text("Currently Connected").font(.caption).foregroundColor(Color.accentColor)
|
||||
// } else if node.lastHeard > 0 {
|
||||
// let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
// Text("Last Heard: \(lastHeard, style: .relative) ago").font(.caption).foregroundColor(.gray)
|
||||
// } else {
|
||||
// Text("Last Heard: Unknown").font(.caption).foregroundColor(.gray)
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
// if connected {
|
||||
// Text("Currently Connected").font(.subheadline).foregroundColor(Color.accentColor)
|
||||
// } else if node.lastHeard > 0 {
|
||||
// let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
|
||||
// Text("Last Heard: \(lastHeard, style: .relative) ago").font(.subheadline).foregroundColor(.gray)
|
||||
// } else {
|
||||
// Text("Last Heard: Unknown").font(.subheadline).foregroundColor(.gray)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }.padding([.leading, .top, .bottom])
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct NodeRow_Previews: PreviewProvider {
|
||||
// static var nodes = BLEManager().meshData.nodes
|
||||
//
|
||||
// static var previews: some View {
|
||||
// Group {
|
||||
// NodeRow(node: nodes[0], connected: true)
|
||||
// }
|
||||
// .previewLayout(.fixed(width: 300, height: 70))
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue