CoreData commit 1 Messages replacement

This commit is contained in:
Garth Vander Houwen 2021-12-12 17:17:46 -08:00
parent 98368640c9
commit 4a08431766
8 changed files with 422 additions and 269 deletions

View file

@ -29,6 +29,7 @@
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 */; };
DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */; };
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; };
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */; };
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5C26ED09490058C060 /* portnums.pb.swift */; };
@ -92,6 +93,7 @@
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = "<group>"; };
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataSample.xcdatamodel; sourceTree = "<group>"; };
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = "<group>"; };
DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = "<group>"; };
DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = "<group>"; };
@ -203,6 +205,7 @@
DDC2E14B26CE248E0042C5E4 = {
isa = PBXGroup;
children = (
DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */,
DDC2E15626CE248E0042C5E4 /* MeshtasticClient */,
DDC2E16D26CE248F0042C5E4 /* MeshtasticClientTests */,
DDC2E17826CE248F0042C5E4 /* MeshtasticClientUITests */,
@ -517,6 +520,7 @@
DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */,
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */,
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */,
DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */,
DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */,
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
@ -883,6 +887,20 @@
productName = SwiftProtobuf;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */
DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */,
);
currentVersion = DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = MeshtasticClient/Meshtastic.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = DDC2E14C26CE248E0042C5E4 /* Project object */;
}

View file

@ -32,38 +32,6 @@
stopOnStyle = "0">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "F27E80D7-F96D-46E5-8456-4F1098121908"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeshtasticClient/Helpers/BLEManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "500"
endingLineNumber = "500"
landmarkName = "peripheral(_:didUpdateValueFor:error:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "DB6E77E0-7B23-469A-B2C0-01FB3B30FBB2"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeshtasticClient/Helpers/BLEManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "456"
endingLineNumber = "456"
landmarkName = "peripheral(_:didUpdateValueFor:error:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
@ -97,15 +65,15 @@
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B8840159-8C82-48AD-85A9-682A3AC08A01"
shouldBeEnabled = "No"
uuid = "C16C366D-F4CF-4FE2-A87D-3A8BBA3A2840"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeshtasticClient/Helpers/BLEManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "400"
endingLineNumber = "400"
startingLineNumber = "555"
endingLineNumber = "555"
landmarkName = "peripheral(_:didUpdateValueFor:error:)"
landmarkType = "7">
</BreakpointContent>

View file

@ -8,25 +8,20 @@ import SwiftUI
// ---------------------------------------------------------------------------------------
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
static let shared = BLEManager()
private static var documentsFolder: URL {
do {
return try FileManager.default.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
} catch {
fatalError("Can't find documents directory.")
}
}
// Core Data
@Environment(\.managedObjectContext) private var viewContext
@Published var meshData: MeshData
@Published var messageData: MessageData
private var centralManager: CBCentralManager!
var context: NSManagedObjectContext?
private var centralManager: CBCentralManager!
@Published var peripherals = [Peripheral]()
@Published var connectedPeripheral: Peripheral!
@Published var connectedNode: NodeInfoModel!
@ -37,13 +32,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
@Published var isScanning: Bool = false
@Published var isConnected: Bool = false
@Published var peripherals = [Peripheral]()
var timeoutTimer: Timer?
var timeoutTimerCount = 0
private var meshLoggingEnabled: Bool = false
private var broadcastNodeId: UInt32 = 4294967295
/* Meshtastic Service Details */
@ -55,24 +46,31 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7")
let FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5")
let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453")
private var meshLoggingEnabled: Bool = false
let meshLog = documentsFolder.appendingPathComponent("meshlog.txt")
/* init BLEManager */
//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.messageData = MessageData()
self.lastConnectedPeripheral = ""
self.lastConnectionError = ""
super.init()
//let bleQueue: DispatchQueue = DispatchQueue(label: "CentralManager")
centralManager = CBCentralManager(delegate: self, queue: nil)
meshData.load()
messageData.load()
//messageData.load()
}
// called when bluetooth is enabled/disabled for the app
// MARK: Bluetooth enabled/disabled for the app
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
@ -84,6 +82,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
// MARK: Scanning for BLE Devices
// Scan for nearby BLE devices using the Meshtastic BLE service ID
func startScanning() {
@ -95,7 +95,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
// Stop Scanning For BLE Devices
// Stop Scanning For BLE Devices
func stopScanning() {
if centralManager.isScanning {
@ -106,6 +106,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
// MARK: BLE Connect functions
/// The action after the timeout-timer has fired
///
/// - Parameters:
@ -160,15 +161,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
RunLoop.current.add(self.timeoutTimer!, forMode: .common)
}
// Disconnect Peripheral function
// Disconnect Connected Peripheral
func disconnectPeripheral() {
guard let connectedPeripheral = connectedPeripheral else { return }
self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
}
// Called each time a peripheral is discovered
//Called each time a peripheral is discovered
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
var peripheralName: String = peripheral.name ?? "Unknown"
@ -197,7 +197,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
// called when a peripheral is connected
// Called when a peripheral is connected
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// guard let connectedPeripheral = connectedPeripheral else { return }
@ -223,6 +223,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
// Called when a Peripheral fails to connect
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
if meshLoggingEnabled { MeshLogger.log("BLE Failed to Connect: \(peripheral.name ?? "Unknown")") }
@ -282,7 +283,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
// Discover Services Event
//MARK: Peripheral Services functions
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let e = error {
@ -303,7 +304,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
// Discover Characteristics Event
//MARK: Discover Characteristics Event
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let e = error {
@ -348,31 +349,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("didUpdateNotificationStateFor char: \(characteristic.uuid.uuidString) \(characteristic.isNotifying)")
if meshLoggingEnabled { MeshLogger.log("didUpdateNotificationStateFor char: \(characteristic.uuid.uuidString) \(characteristic.isNotifying)") }
if let errorText = error?.localizedDescription {
print("didUpdateNotificationStateFor error: \(errorText)")
}
// commandLock.lock()
// if let index = commandConditions.firstIndex(where: { (condition) -> Bool in
// if case .notificationStateUpdate(characteristic: characteristic, enabled: characteristic.isNotifying) = condition {
// return true
// } else {
// return false
// }
// }) {
// commandConditions.remove(at: index)
// commandError = error
// if commandConditions.isEmpty {
// commandLock.broadcast()
// }
// }
// commandLock.unlock()
// ?.peripheralManager(self, didUpdateNotificationStateFor: characteristic)
}
}
// Data Read / Update Characteristic Event
//MARK: Data Read / Update Characteristic Event
//TODO: Convert to CoreData
//FIXME: Remove broken JSON file data layer implementation
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let e = error {
@ -383,10 +368,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
switch characteristic.uuid {
case FROMNUM_UUID:
peripheral.readValue(for: FROMNUM_characteristic)
// let byteArrayFromData: [UInt8] = [UInt8](characteristic.value!)
// let stringFromByteArray = String(data: Data(_: byteArrayFromData), encoding: .utf8)
// print("string array data \(stringFromByteArray!)")
// print(characteristic.value?. ?? "no value")
let characteristicValue: [UInt8] = [UInt8](characteristic.value!)
let bigEndianUInt32 = characteristicValue.withUnsafeBytes { $0.load(as: UInt32.self) }
let returnValue = CFByteOrderGetCurrent() == CFByteOrder(CFByteOrderLittleEndian.rawValue)
? UInt32(bigEndian: bigEndianUInt32) : bigEndianUInt32
//print(returnValue)
case FROMRADIO_UUID:
if characteristic.value == nil || characteristic.value!.isEmpty {
@ -400,74 +386,220 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("Print DecodedInfo")
print(decodedInfo)
// MyInfo Data
if decodedInfo.myInfo.myNodeNum != 0 {
// Create a MyInfoModel
let myInfoModel = MyInfoModel(
myNodeNum: decodedInfo.myInfo.myNodeNum,
hasGps: decodedInfo.myInfo.hasGps_p,
numBands: decodedInfo.myInfo.numBands,
maxChannels: decodedInfo.myInfo.maxChannels,
firmwareVersion: decodedInfo.myInfo.firmwareVersion,
messageTimeoutMsec: decodedInfo.myInfo.messageTimeoutMsec,
minAppVersion: decodedInfo.myInfo.minAppVersion)
// Save it to the connected nodeInfo
if connectedPeripheral != nil {
connectedPeripheral.myInfo = myInfoModel
// Save it to the connected node
connectedNode = meshData.nodes.first(where: {$0.num == myInfoModel.myNodeNum})
}
// Since the data is from the device itself we save all myInfo objects since they are always the most up to date
if connectedNode != nil {
connectedNode.myInfo = myInfoModel
let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.myInfo.myNodeNum })
// meshData.nodes.remove(at: nodeIndex!)
// meshData.nodes.append(connectedNode)
if nodeIndex != nil {
meshData.nodes[nodeIndex!] = connectedNode
meshData.save()
print("Save a CoreData MyInfoEntity")
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
if fetchedMyInfo.isEmpty {
let myInfo = MyInfoEntity(context: context!)
myInfo.myNodeNum = Int64(decodedInfo.myInfo.myNodeNum)
myInfo.hasGps = decodedInfo.myInfo.hasGps_p
myInfo.numBands = Int32(bitPattern: decodedInfo.myInfo.numBands)
myInfo.firmwareVersion = decodedInfo.myInfo.firmwareVersion
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)")
}
}
print("Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)")
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? "Unknown")") }
} catch {
}
//if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? String(myInfo.myNodeNum))") }
}
// NodeInfo Data
if decodedInfo.nodeInfo.num != 0 {
print("Save a CoreData NodeInfoEntity")
let fetchNodeRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeRequest.predicate = NSPredicate(format: "num == %i", Int64(decodedInfo.nodeInfo.num))
do {
let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity]
// Not Found Insert
if fetchedNode.isEmpty {
let newNode = NodeInfoEntity(context: context!)
newNode.timestamp = Date()
newNode.id = Int64(decodedInfo.nodeInfo.num)
newNode.num = Int64(decodedInfo.nodeInfo.num)
newNode.lastHeard = Int32(bitPattern: decodedInfo.nodeInfo.lastHeard)
newNode.snr = decodedInfo.nodeInfo.snr
let userIdLast4: String = String(decodedInfo.nodeInfo.user.id.suffix(4))
newNode.bleName = "Meshtastic_" + userIdLast4
if decodedInfo.nodeInfo.hasUser {
print("Save a nodeInfo")
print(decodedInfo.nodeInfo)
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()
let newUser = UserEntity(context: context!)
newUser.userId = decodedInfo.nodeInfo.user.id
newUser.num = Int64(decodedInfo.nodeInfo.num)
newUser.longName = decodedInfo.nodeInfo.user.longName
newUser.shortName = decodedInfo.nodeInfo.user.shortName
newUser.macaddr = decodedInfo.nodeInfo.user.macaddr
newUser.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased()
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)
}
// Look for a MyInfo
let fetchMyInfoRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %i", Int64(decodedInfo.nodeInfo.num))
do {
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
if fetchedMyInfo.count > 0 {
newNode.myInfo = fetchedMyInfo[0]
}
} catch {
print("Fetch MyInfo Error")
}
} else {
let updatedNode = fetchedNode[0]
updatedNode.timestamp = Date()
updatedNode.lastHeard = Int32(bitPattern: decodedInfo.nodeInfo.lastHeard)
updatedNode.snr = decodedInfo.nodeInfo.snr
if decodedInfo.nodeInfo.hasUser {
// Data is older than what the app already has
return
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
}
}
// Look for a MyInfo
let fetchMyInfoRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %i", Int64(decodedInfo.nodeInfo.num))
do {
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
if fetchedMyInfo.count > 0 {
updatedNode.myInfo = fetchedMyInfo[0]
}
} catch {
print("Fetch MyInfo Error")
}
}
do {
try context!.save()
print("Saved a nodeInfo for \(decodedInfo.nodeInfo.num)")
} catch {
context!.rollback()
let nsError = error as NSError
print("Error Saving CoreData NodeInfoEntity: \(nsError)")
}
} catch {
print("Fetch NodeInfoEntity Error")
}
if decodedInfo.nodeInfo.hasUser {
print("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)")
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") }
} else {
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)") }
}
// 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 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(
@ -493,11 +625,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
)
meshData.save()
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") }
if connectedNode == nil {
// connectedNode = meshData.nodes.first(where: {$0.num == connectedPeripheral.myInfo!.myNodeNum})
}
}
}
// Handle assorted app packets
@ -505,6 +632,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("Handle a Packet")
do {
//!!!: Switch Messages Tab to coredata
// Text Message App - Primary Broadcast Channel
if decodedInfo.packet.decoded.portnum == PortNum.textMessageApp {
@ -512,50 +641,71 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("Message Text: \(messageText)")
if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received for text message app \(messageText)") }
let messageUsers:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
messageUsers.predicate = NSPredicate(format:"num IN %@", [decodedInfo.packet.to, decodedInfo.packet.from])
do {
let fetchedUsers = try context?.fetch(messageUsers) as! [UserEntity]
let newMessage = MessageEntity(context: context!)
newMessage.messageId = Int32(bitPattern: decodedInfo.packet.id)
newMessage.messageTimestamp = Int32(bitPattern: decodedInfo.packet.rxTime)
newMessage.receivedACK = false
newMessage.direction = "IN"
if decodedInfo.packet.to == broadcastNodeId {
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
} else {
newMessage.toUser = fetchedUsers.first(where: { $0.num == decodedInfo.packet.to })
}
newMessage.fromUser = fetchedUsers.first(where: { $0.num == decodedInfo.packet.from })
newMessage.messagePayload = messageText
do {
try context!.save()
print("Saved a new message for \(decodedInfo.packet.id)")
// Create an iOS Notification for the received message and schedule it immediately
let manager = LocalNotificationManager()
let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from })
var toUserLongName: String = "Broadcast"
var toUserShortName: String = "BC"
if decodedInfo.packet.to != broadcastNodeId {
let toUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from })
toUserLongName = toUser?.user.longName ?? "Unknown"
toUserShortName = toUser?.user.shortName ?? "???"
manager.notifications = [
Notification(
id: ("notification.id.\(decodedInfo.packet.id)"),
title: "\(newMessage.fromUser?.longName ?? "Unknown")",
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
content: messageText)
]
manager.schedule()
if meshLoggingEnabled { MeshLogger.log("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown") \(messageText)") }
} catch {
print("Something went wrong: \(error)")
context!.rollback()
let nsError = error as NSError
print("Unresolved error \(nsError)")
}
} catch {
print("Fetch Message To and From Users Error")
}
// Add the received message to the local messages list / file and save
messageData.messages.append(
MessageModel(
messageId: decodedInfo.packet.id,
messageTimeStamp: decodedInfo.packet.rxTime,
fromUserId: decodedInfo.packet.from,
toUserId: decodedInfo.packet.to,
fromUserLongName: fromUser?.user.longName ?? "Unknown",
toUserLongName: toUserLongName,
fromUserShortName: fromUser?.user.shortName ?? "???",
toUserShortName: toUserShortName,
receivedACK: decodedInfo.packet.decoded.wantResponse,
messagePayload: messageText,
direction: "IN")
)
messageData.save()
// Create an iOS Notification for the received message and schedule it immediately
let manager = LocalNotificationManager()
manager.notifications = [
Notification(
id: ("notification.id.\(decodedInfo.packet.id)"),
title: "\(fromUser?.user.longName ?? "Unknown")",
subtitle: "AKA \(fromUser?.user.shortName ?? "???")",
content: messageText)
]
manager.schedule()
if meshLoggingEnabled { MeshLogger.log("iOS Notification Scheduled for text message from \(fromUser?.user.longName ?? "Unknown") \(messageText)") }
}
} else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp {
} else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp {
var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from })
if updatedNode != nil {
@ -684,8 +834,8 @@ 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()
//messageData.messages.append(messageModel)
//messageData.save()
success = true
}
}

View file

@ -3,8 +3,10 @@ import CoreData
@main
struct MeshtasticClientApp: App {
@ObservedObject private var bleManager: BLEManager = BLEManager()
let persistenceController = PersistenceController.shared
@ObservedObject private var bleManager: BLEManager = BLEManager.shared
@ObservedObject private var userSettings: UserSettings = UserSettings()
@Environment(\.scenePhase) var scenePhase
@ -12,6 +14,7 @@ struct MeshtasticClientApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(userSettings)
.environmentObject(bleManager)
}

View file

@ -6,52 +6,54 @@
//
import CoreData
/*
struct PersistenceController {
static let shared = PersistenceController()
class PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: false)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = NodeInfoEntity(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: false)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = NodeInfoEntity(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Meshtastic")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Meshtastic")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
// Merge policy that favors in memory data over data in the db
self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
*/

View file

@ -15,7 +15,8 @@ import CoreBluetooth
struct Connect: View {
@EnvironmentObject var bleManager: BLEManager
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@State var isPreferredRadio: Bool = false
@ -60,8 +61,8 @@ struct Connect: View {
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.myInfo != nil {
Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.myInfo?.firmwareVersion ?? "(null)").font(.caption).foregroundColor(Color.gray)
if bleManager.connectedPeripheral != nil {
//Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.myInfo?.firmwareVersion ?? "(null)").font(.caption).foregroundColor(Color.gray)
}
if bleManager.connectedPeripheral.subscribed {
Text("Properly Subscribed").font(.caption)
@ -207,6 +208,8 @@ struct Connect: View {
.navigationViewStyle(StackNavigationViewStyle())
.onAppear(perform: {
self.bleManager.context = context
if bleManager.connectedPeripheral != nil && userSettings.preferredPeripheralId == bleManager.connectedPeripheral.peripheral.identifier.uuidString {
isPreferredRadio = true
} else {

View file

@ -8,6 +8,15 @@ struct Messages: View {
enum Field: Hashable {
case messageText
}
// CoreData
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \MessageEntity.messageTimestamp, ascending: true)],
animation: .default)
private var messages: FetchedResults<MessageEntity>
// Keyboard State
@State var typingMessage: String = ""
@ -16,14 +25,8 @@ struct Messages: View {
@State private var lastTypingMessage = ""
@FocusState private var focusedField: Field?
@Namespace var topId
@Namespace var bottomId
@State var showDeleteMessageAlert = false
@State private var deleteMessageId: UInt32 = 0
// Message Data and Bluetooth
@EnvironmentObject var bleManager: BLEManager
@State private var deleteMessageId: Int32 = 0
public var broadcastNodeId: UInt32 = 4294967295
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@ -38,18 +41,16 @@ struct Messages: View {
ScrollViewReader { scrollView in
if self.bleManager.messageData.messages.count > 0 {
if self.messages.count > 0 {
ScrollView {
Text("Hidden Top Anchor").hidden().frame(height: 0).id(topId)
ForEach(bleManager.messageData.messages.sorted(by: { $0.messageTimestamp < $1.messageTimestamp })) { message in
ForEach(messages) { message in
HStack(alignment: .top) {
let currentUser: Bool = (bleManager.connectedNode != nil) && ((bleManager.connectedNode.id) == message.fromUserId)
let currentUser: Bool = false//(bleManager.connectedNode != nil) && ((bleManager.connectedNode.id) == message.fromUser!.num)
CircleText(text: message.fromUserShortName, color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
CircleText(text: "???", color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
.gesture(LongPressGesture(minimumDuration: 2)
.onEnded {_ in
print("I want to delete message: \(message.messageId)")
@ -59,7 +60,7 @@ struct Messages: View {
})
VStack(alignment: .leading) {
Text(message.messagePayload)
Text(message.messagePayload ?? "EMPTY MESSAGE")
.textSelection(.enabled)
.padding(10)
.foregroundColor(.white)
@ -87,11 +88,12 @@ struct Messages: View {
print("OK button tapped")
if deleteMessageId > 0 {
let messageIndex = bleManager.messageData.messages.firstIndex(where: { $0.messageId == deleteMessageId })
bleManager.messageData.messages.remove(at: messageIndex!)
bleManager.messageData.save()
print("Deleted message: \(message.messageId)")
showDeleteMessageAlert = false
//let message = messages.first.where: { $0.messageId == deleteMessageId })
//context.delete(object: message)
//bleManager.messageData.messages.remove(at: messageIndex!)
//bleManager.messageData.save()
//print("Deleted message: \(message.messageId)")
//showDeleteMessageAlert = false
deleteMessageId = 0
}
},
@ -99,16 +101,23 @@ struct Messages: View {
)
}
}
.onAppear(perform: { scrollView.scrollTo(bottomId) })
Text("Hidden Bottom Anchor").hidden().frame(height: 0).id(bottomId)
.onAppear(perform: {
self.bleManager.context = context
messageCount = messages.count
if messageCount > 0 {
scrollView.scrollTo(messages[messageCount-1].id, anchor: .bottom)
}
})
}
.onReceive(timer) { _ in
if messageCount < bleManager.messageData.messages.count {
if messageCount < messages.count {
scrollView.scrollTo(messages[messageCount].id, anchor: .bottom)
messageCount = messages.count
bleManager.messageData.load()
scrollView.scrollTo(bottomId)
messageCount = bleManager.messageData.messages.count
}
}
.padding(.horizontal)
@ -193,7 +202,7 @@ struct Messages: View {
})
.onAppear {
messageCount = bleManager.messageData.messages.count
messageCount = messages.count
}
}
}

View file

@ -68,7 +68,7 @@ class UserSettings: ObservableObject {
self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? ""
self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false
self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0
self.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? true
self.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false
}
}