diff --git a/.swiftlint.yml b/.swiftlint.yml index 9471fd63..a1e7b51a 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -45,6 +45,7 @@ disabled_rules: # rule identifiers to exclude from running - operator_whitespace - multiple_closures_with_trailing_closure - todo + - trailing_whitespace # TODO: should review nesting: diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f51a741e..9c2434c1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -67,7 +67,7 @@ DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */; }; - DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */; }; + DD3CC6C228EB9D4900FA9159 /* UpdateCoreDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6C128EB9D4900FA9159 /* UpdateCoreDataController.swift */; }; DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; }; DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; }; @@ -109,7 +109,7 @@ DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; }; DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */; }; DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC32974767D007C176F /* MapViewFitExtension.swift */; }; - DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreData.swift */; }; + DD964FC62975DBFD007C176F /* QueryCoreDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreDataController.swift */; }; DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; }; DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; }; DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; }; @@ -292,7 +292,7 @@ DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingEnums.swift; sourceTree = ""; }; - DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoreData.swift; sourceTree = ""; }; + DD3CC6C128EB9D4900FA9159 /* UpdateCoreDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoreDataController.swift; sourceTree = ""; }; DD3D17DC2C3D7B1400561584 /* MeshtasticDataModelV 39.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 39.xcdatamodel"; sourceTree = ""; }; DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; @@ -344,7 +344,7 @@ DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = ""; }; DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = ""; }; DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = ""; }; - DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = ""; }; + DD964FC52975DBFD007C176F /* QueryCoreDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreDataController.swift; sourceTree = ""; }; DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = ""; }; DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = ""; }; DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = ""; }; @@ -869,8 +869,8 @@ isa = PBXGroup; children = ( DDC4D567275499A500A4208E /* Persistence.swift */, - DD964FC52975DBFD007C176F /* QueryCoreData.swift */, - DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */, + DD964FC52975DBFD007C176F /* QueryCoreDataController.swift */, + DD3CC6C128EB9D4900FA9159 /* UpdateCoreDataController.swift */, ); path = Persistence; sourceTree = ""; @@ -1188,7 +1188,7 @@ DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, - DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */, + DD3CC6C228EB9D4900FA9159 /* UpdateCoreDataController.swift in Sources */, DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */, DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, DDB8F4142A9EE5F000230ECE /* ChannelList.swift in Sources */, @@ -1223,7 +1223,7 @@ DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */, - DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */, + DD964FC62975DBFD007C176F /* QueryCoreDataController.swift in Sources */, DDB75A112A059258006ED576 /* Url.swift in Sources */, DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index f15ba040..0a92a327 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -11,10 +11,12 @@ import OSLog // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject { - - var context: NSManagedObjectContext? - - static let shared = BLEManager() + + private let context: NSManagedObjectContext + private let updateCoreDataController: UpdateCoreDataController + private let queryCoreDataController: QueryCoreDataController + + private var centralManager: CBCentralManager! @Published var peripherals: [Peripheral] = [] @Published var connectedPeripheral: Peripheral! @@ -51,17 +53,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") let LEGACY_LOGRADIO_UUID = CBUUID(string: "0x6C6FD238-78FA-436B-AACF-15C5BE1EF2E2") let LOGRADIO_UUID = CBUUID(string: "0x5a3d6e49-06e6-4423-9944-e9de8cdf9547") - - // MARK: init BLEManager - override init() { + + // MARK: init + + init( + context: NSManagedObjectContext, + updateCoreDataController: UpdateCoreDataController, + queryCoreDataController: QueryCoreDataController + ) { self.lastConnectionError = "" self.connectedVersion = "0.0.0" + self.context = context + self.updateCoreDataController = updateCoreDataController + self.queryCoreDataController = queryCoreDataController super.init() centralManager = CBCentralManager(delegate: self, queue: nil) mqttManager.delegate = self - // centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey]) } - + // MARK: Scanning for BLE Devices // Scan for nearby BLE devices using the Meshtastic BLE service ID func startScanning() { @@ -70,7 +79,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("✅ [BLE] Scanning Started") } } - + // Stop Scanning For BLE Devices func stopScanning() { if centralManager.isScanning { @@ -78,7 +87,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("🛑 [BLE] Stopped Scanning") } } - + // MARK: BLE Connect functions /// The action after the timeout-timer has fired /// @@ -88,17 +97,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @objc func timeoutTimerFired(timer: Timer) { guard let timerContext = timer.userInfo as? [String: String] else { return } let name: String = timerContext["name", default: "Unknown"] - + self.timeoutTimerCount += 1 self.lastConnectionError = "" - + if timeoutTimerCount == 10 { if connectedPeripheral != nil { self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } connectedPeripheral = nil if self.timeoutTimer != nil { - + self.timeoutTimer!.invalidate() } self.isConnected = false @@ -111,7 +120,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("🚨 [BLE] Connecting 2 Second Timeout Timer Fired \(self.timeoutTimerCount, privacy: .public) Time(s): \(name, privacy: .public)") } } - + // Connect to a specific peripheral func connectTo(peripheral: CBPeripheral) { stopScanning() @@ -124,7 +133,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("ℹ️ [BLE] Disconnecting from: \(self.connectedPeripheral.name, privacy: .public) to connect to \(peripheral.name ?? "Unknown", privacy: .public)") disconnectPeripheral() } - + centralManager?.connect(peripheral) // Invalidate any existing timer if timeoutTimer != nil { @@ -137,10 +146,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate RunLoop.current.add(timeoutTimer!, forMode: .common) Logger.services.info("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown", privacy: .public)") } - + // Disconnect Connected Peripheral func cancelPeripheralConnection() { - + if mqttProxyConnected { mqttManager.mqttClientProxy?.disconnect() } @@ -159,10 +168,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate stopScanning() startScanning() } - + // Disconnect Connected Peripheral func disconnectPeripheral(reconnect: Bool = true) { - + guard let connectedPeripheral = connectedPeripheral else { return } if mqttProxyConnected { mqttManager.mqttClientProxy?.disconnect() @@ -177,7 +186,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate stopScanning() startScanning() } - + // Called each time a peripheral is connected func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { isConnecting = false @@ -190,7 +199,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if timeoutTimer != nil { timeoutTimer!.invalidate() } - + // remove any connection errors self.lastConnectionError = "" // Map the peripheral to the connectedPeripheral ObservedObjects @@ -207,13 +216,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate peripheral.discoverServices([meshtasticServiceCBUUID]) Logger.services.info("✅ [BLE] Connected: \(peripheral.name ?? "Unknown", privacy: .public)") } - + // Called when a Peripheral fails to connect func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { cancelPeripheralConnection() Logger.services.error("🚫 [BLE] Failed to Connect: \(peripheral.name ?? "Unknown", privacy: .public)") } - + // Disconnect Peripheral Event func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { self.connectedPeripheral = nil @@ -274,7 +283,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() } - + // MARK: Peripheral Services functions func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let error { @@ -286,46 +295,46 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("✅ [BLE] Service for Meshtastic discovered by \(peripheral.name ?? "Unknown", privacy: .public)") } } - + // MARK: Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { - + if let error { Logger.services.error("🚫 [BLE] Discover Characteristics error for \(peripheral.name ?? "Unknown", privacy: .public) \(error.localizedDescription, privacy: .public) disconnecting device") // Try and stop crashes when this error occurs disconnectPeripheral() return } - + guard let characteristics = service.characteristics else { return } - + for characteristic in characteristics { switch characteristic.uuid { - + case TORADIO_UUID: Logger.services.info("✅ [BLE] did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") TORADIO_characteristic = characteristic - + case FROMRADIO_UUID: Logger.services.info("✅ [BLE] did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") FROMRADIO_characteristic = characteristic peripheral.readValue(for: FROMRADIO_characteristic) - + case FROMNUM_UUID: Logger.services.info("✅ [BLE] did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") FROMNUM_characteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) - + case LEGACY_LOGRADIO_UUID: Logger.services.info("✅ [BLE] did discover legacy LOGRADIO (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") LEGACY_LOGRADIO_characteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) - + case LOGRADIO_UUID: Logger.services.info("✅ [BLE] did discover LOGRADIO (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") LOGRADIO_characteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) - + default: break } @@ -337,7 +346,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate sendWantConfig() } } - + // MARK: MqttClientProxyManagerDelegate Methods func onMqttConnected() { mqttProxyConnected = true @@ -345,14 +354,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("📲 [MQTT Client Proxy] onMqttConnected now subscribing to \(self.mqttManager.topic, privacy: .public).") mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) } - + func onMqttDisconnected() { mqttProxyConnected = false Logger.services.info("📲 MQTT Disconnected") } - + func onMqttMessageReceived(message: CocoaMQTTMessage) { - + if message.topic.contains("/stat/") { return } @@ -360,7 +369,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate proxyMessage.topic = message.topic proxyMessage.data = Data(message.payload) proxyMessage.retained = message.retained - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.mqttClientProxyMessage = proxyMessage @@ -371,18 +380,18 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) } } - + func onMqttError(message: String) { mqttProxyConnected = false mqttError = message Logger.services.info("📲 [MQTT Client Proxy] onMqttError: \(message, privacy: .public)") } - + // MARK: Protobuf Methods func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 { - + guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 } - + var adminPacket = AdminMessage() adminPacket.getDeviceMetadataRequest = true var meshPacket: MeshPacket = MeshPacket() @@ -401,19 +410,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } else { return 0 } - + let messageDescription = "🛎️ [Device Metadata] Requested for node \(toUser.longName ?? "unknown".localized) by \(fromUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return Int64(meshPacket.id) } return 0 } - + func sendTraceRouteRequest(destNum: Int64, wantResponse: Bool) -> Bool { - + var success = false guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return success } - + let fromNodeNum = connectedPeripheral.num let routePacket = RouteDiscovery() var meshPacket = MeshPacket() @@ -439,12 +448,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - - let traceRoute = TraceRouteEntity(context: context!) + + let traceRoute = TraceRouteEntity(context: context) let nodes = NodeInfoEntity.fetchRequest() nodes.predicate = NSPredicate(format: "num IN %@", [destNum, self.connectedPeripheral.num]) do { - let fetchedNodes = try context!.fetch(nodes) + let fetchedNodes = try context.fetch(nodes) let receivingNode = fetchedNodes.first(where: { $0.num == destNum }) let connectedNode = fetchedNodes.first(where: { $0.num == self.connectedPeripheral.num }) traceRoute.id = Int64(meshPacket.id) @@ -460,33 +469,33 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } do { - try context!.save() + try context.save() Logger.data.info("💾 Saved TraceRoute sent to node: \(String(receivingNode?.user?.longName ?? "unknown".localized), privacy: .public)") } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("Error Updating Core Data BluetoothConfigEntity: \(nsError, privacy: .public)") } - + let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, destNum.toHex()) MeshLogger.log("🪧 \(logString)") - + } catch { - + } } return success } - + func sendWantConfig() { guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return } - + if FROMRADIO_characteristic == nil { MeshLogger.log("🚨 \("firmware.version.unsupported".localized)") invalidVersion = true return } else { - + let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized let logString = String.localizedStringWithFormat("mesh.log.wantconfig %@".localized, nodeName) MeshLogger.log("🛎️ \(logString)") @@ -503,7 +512,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic) } } - + func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let error { Logger.services.error("💥 [BLE] didUpdateNotificationStateFor error: \(characteristic.uuid, privacy: .public) \(error.localizedDescription, privacy: .public)") @@ -511,7 +520,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.services.info("ℹ️ [BLE] peripheral didUpdateNotificationStateFor \(characteristic.uuid, privacy: .public)") } } - + fileprivate func handleRadioLog(radioLog: String) { var log = radioLog /// Debug Log Level @@ -561,9 +570,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { - + if let error { - + Logger.services.error("🚫 [BLE] didUpdateValueFor Characteristic error \(error.localizedDescription, privacy: .public)") let errorCode = (error as NSError).code if errorCode == 5 || errorCode == 15 { @@ -576,7 +585,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } - + switch characteristic.uuid { case LOGRADIO_UUID: if characteristic.value == nil || characteristic.value!.isEmpty { @@ -591,11 +600,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .info: message = "INFO | \(message)" case .warning: - message = "WARN | \(message)" + message = "WARN | \(message)" case .error: - message = "ERROR | \(message)" + message = "ERROR | \(message)" case .critical: - message = "CRIT | \(message)" + message = "CRIT | \(message)" default: message = "DEBUG | \(message)" } @@ -604,7 +613,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate catch { // Ignore fail to parse as LogRecord } - + case LEGACY_LOGRADIO_UUID: if characteristic.value == nil || characteristic.value!.isEmpty { return @@ -612,21 +621,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if let log = String(data: characteristic.value!, encoding: .utf8) { handleRadioLog(radioLog: log) } - + case FROMRADIO_UUID: - + if characteristic.value == nil || characteristic.value!.isEmpty { return } var decodedInfo = FromRadio() - + do { decodedInfo = try FromRadio(serializedData: characteristic.value!) - + } catch { Logger.services.error("💥 \(error.localizedDescription, privacy: .public) \(characteristic.value!, privacy: .public)") } - + // Publish mqttClientProxyMessages received on the from radio if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.mqttClientProxyMessage(decodedInfo.mqttClientProxyMessage) { let message = CocoaMQTTMessage( @@ -636,20 +645,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate ) mqttManager.mqttClientProxy?.publish(message) } - + switch decodedInfo.packet.decoded.portnum { - + // Handle Any local only packets we get over BLE case .unknownApp: var nowKnown = false - guard let ctx = context else { - return - } - // MyInfo from initial connection if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { - let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: ctx) - + let myInfo = updateCoreDataController.myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id) + if myInfo != nil { UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) connectedPeripheral.num = myInfo?.myNodeNum ?? 0 @@ -666,9 +671,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate do { disconnectPeripheral(reconnect: false) try container.restorePersistentStore(from: databasePath) - context?.refreshAllObjects() + context.refreshAllObjects() let request = MyInfoEntity.fetchRequest() - try context?.fetch(request) + try context.fetch(request) UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) connectTo(peripheral: peripheral) Logger.data.notice("🗂️ Restored Core data for /\(UserDefaults.preferredPeripheralNum, privacy: .public)") @@ -684,7 +689,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // NodeInfo if decodedInfo.nodeInfo.num > 0 { nowKnown = true - if let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: ctx) { + if let nodeInfo = updateCoreDataController.nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel) { if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo.num { if nodeInfo.user != nil { connectedPeripheral.shortName = nodeInfo.user?.shortName ?? "?" @@ -696,17 +701,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Channels if decodedInfo.channel.isInitialized && connectedPeripheral != nil { nowKnown = true - channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: ctx) + updateCoreDataController.channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num)) } // Config if decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil { nowKnown = true - localConfig(config: decodedInfo.config, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName) + updateCoreDataController.localConfig(config: decodedInfo.config, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName) } // Module Config if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0 { nowKnown = true - moduleConfig(config: decodedInfo.moduleConfig, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) + updateCoreDataController.moduleConfig(config: decodedInfo.moduleConfig, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { if decodedInfo.moduleConfig.cannedMessage.enabled { _ = self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true) @@ -716,7 +721,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Device Metadata if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion { nowKnown = true - deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: ctx) + updateCoreDataController.deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num) connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion let lastDotIndex = decodedInfo.metadata.firmwareVersion.lastIndex(of: ".") if lastDotIndex == nil { @@ -739,22 +744,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } case .textMessageApp, .detectionSensorApp: - textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + updateCoreDataController.textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0)) case .remoteHardwareApp: MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .positionApp: - upsertPositionPacket(packet: decodedInfo.packet, context: context!) + updateCoreDataController.upsertPositionPacket(packet: decodedInfo.packet) case .waypointApp: - waypointPacket(packet: decodedInfo.packet, context: context!) + updateCoreDataController.waypointPacket(packet: decodedInfo.packet) case .nodeinfoApp: - if !invalidVersion { upsertNodeInfoPacket(packet: decodedInfo.packet, context: context!) } + if !invalidVersion { + updateCoreDataController.upsertNodeInfoPacket(packet: decodedInfo.packet) + } case .routingApp: - if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context!) } + if !invalidVersion { updateCoreDataController.routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num) } case .adminApp: - adminAppPacket(packet: decodedInfo.packet, context: context!) + updateCoreDataController.adminAppPacket(packet: decodedInfo.packet) case .replyApp: MeshLogger.log("🕸️ MESH PACKET received for Reply App handling as a text message") - textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + updateCoreDataController.textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0)) case .ipTunnelApp: // MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED") @@ -762,19 +769,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED UNHANDLED") case .storeForwardApp: - if wantStoreAndForwardPackets { - storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + if let connectedPeripheral, wantStoreAndForwardPackets { + storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: connectedPeripheral.num, context: context) } else { MeshLogger.log("🕸️ MESH PACKET received for Store and Forward App - Store and Forward is disabled.") } case .rangeTestApp: if wantRangeTestPackets { - textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: true, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + updateCoreDataController.textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: true, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0)) } else { MeshLogger.log("🕸️ MESH PACKET received for Range Test App Range testing is disabled.") } case .telemetryApp: - if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } + if !invalidVersion { + updateCoreDataController.telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0)) + } case .textMessageCompressedApp: // MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED") @@ -795,22 +804,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { - let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context!) + let traceRoute = queryCoreDataController.getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID)) traceRoute?.response = true traceRoute?.route = routingMessage.route if routingMessage.route.count == 0 { let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(decodedInfo.packet.from)) MeshLogger.log("🪧 \(logString)") - + } else { var routeString = "You --> " var hopNodes: [TraceRouteHopEntity] = [] for node in routingMessage.route { - var hopNode = getNodeInfo(id: Int64(node), context: context!) + var hopNode = queryCoreDataController.getNodeInfo(id: Int64(node)) if hopNode == nil && hopNode?.num ?? 0 > 0 { - hopNode = createNodeInfo(num: Int64(node), context: context!) + hopNode = createNodeInfo(num: Int64(node), context: context) } - let traceRouteHop = TraceRouteHopEntity(context: context!) + let traceRouteHop = TraceRouteHopEntity(context: context) traceRouteHop.time = Date() if hopNode?.hasPositions ?? false { traceRoute?.hasPositions = true @@ -838,10 +847,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRoute?.routeText = routeString traceRoute?.hops = NSOrderedSet(array: hopNodes) do { - try context!.save() + try context.save() Logger.data.info("💾 Saved Trace Route") } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("Error Updating Core Data TraceRouteHOp: \(nsError, privacy: .public)") } @@ -855,7 +864,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \(neighborInfo)") } case .paxcounterApp: - paxCounterPacket(packet: decodedInfo.packet, context: context!) + updateCoreDataController.paxCounterPacket(packet: decodedInfo.packet) case .mapReportApp: MeshLogger.log("🕸️ MESH PACKET received Map Report App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .UNRECOGNIZED: @@ -867,7 +876,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .powerstressApp: MeshLogger.log("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } - + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { invalidVersion = false lastConnectionError = "" @@ -875,14 +884,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID)") peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again - + /// MQTT Client Proxy and RangeTest and Store and Forward interest if connectedPeripheral.num > 0 { - + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num)) do { - let fetchedNodeInfo = try context?.fetch(fetchNodeInfoRequest) ?? [] + let fetchedNodeInfo = try context.fetch(fetchNodeInfoRequest) if fetchedNodeInfo.count == 1 { // Subscribe to Mqtt Client Proxy if enabled if fetchedNodeInfo[0].mqttConfig != nil && fetchedNodeInfo[0].mqttConfig?.enabled ?? false && fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false { @@ -898,7 +907,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages ?? 0 // appState.connectedNode = fetchedNodeInfo[0] UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - + } if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true { wantRangeTestPackets = true @@ -906,12 +915,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].storeForwardConfig?.enabled == true { wantStoreAndForwardPackets = true } - + } catch { Logger.data.error("Failed to find a node info for the connected node \(error.localizedDescription)") } } - + // MARK: Share Location Position Update Timer // Use context to pass the radio name with the timer // Use a RunLoop to prevent the timer from running on the main UI thread @@ -924,7 +933,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } - + case FROMNUM_UUID: Logger.services.info("🗞️ [BLE] (Notify) characteristic value will be read next") default: @@ -935,16 +944,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate peripheral.readValue(for: FROMRADIO_characteristic) } } - + public func sendMessage(message: String, toUserNum: Int64, channel: Int32, isEmoji: Bool, replyID: Int64) -> Bool { var success = false - + // Return false if we are not properly connected to a device, handle retry logic in the view for now if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected { - + self.disconnectPeripheral() self.startScanning() - + // Try and connect to the preferredPeripherial first let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.preferredPeripheralId as String }).first if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { @@ -953,30 +962,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized let logString = String.localizedStringWithFormat("mesh.log.textmessage.send.failed %@".localized, nodeName) MeshLogger.log("🚫 \(logString)") - + success = false } else if message.count < 1 { - + // Don't send an empty message Logger.mesh.info("🚫 Don't Send an Empty Message") success = false - + } else { - guard let context else { return false } let fromUserNum: Int64 = self.connectedPeripheral.num - + let messageUsers = UserEntity.fetchRequest() messageUsers.predicate = NSPredicate(format: "num IN %@", [fromUserNum, Int64(toUserNum)]) - + do { - + let fetchedUsers = try context.fetch(messageUsers) if fetchedUsers.isEmpty { - + Logger.data.error("🚫 Message Users Not Found, Fail") success = false } else if fetchedUsers.count >= 1 { - + let newMessage = MessageEntity(context: context) newMessage.messageId = Int64(UInt32.random(in: UInt32(UInt8.max).. 0 { @@ -1021,7 +1029,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded.replyID = UInt32(replyID) } meshPacket.wantAck = true - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket @@ -1031,13 +1039,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) let logString = String.localizedStringWithFormat("mesh.log.textmessage.sent %@ %@ %@".localized, String(newMessage.messageId), fromUserNum.toHex(), toUserNum.toHex()) - + MeshLogger.log("💬 \(logString)") do { try context.save() Logger.data.info("💾 Saved a new sent message from \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)") success = true - + } catch { context.rollback() let nsError = error as NSError @@ -1046,12 +1054,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } } catch { - + } } return success } - + public func sendWaypoint(waypoint: Waypoint) -> Bool { if waypoint.latitudeI == 373346000 && waypoint.longitudeI == -1220090000 { return false @@ -1069,7 +1077,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Could not serialiaze the payload return false } - + dataMessage.portnum = PortNum.waypointApp meshPacket.decoded = dataMessage var toRadio: ToRadio! @@ -1083,7 +1091,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let wayPointEntity = getWaypoint(id: Int64(waypoint.id), context: context!) + let wayPointEntity = queryCoreDataController.getWaypoint(id: Int64(waypoint.id)) wayPointEntity.id = Int64(waypoint.id) wayPointEntity.name = waypoint.name.count >= 1 ? waypoint.name : "Dropped Pin" wayPointEntity.longDescription = waypoint.description_p @@ -1106,21 +1114,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate wayPointEntity.lastUpdated = Date() } do { - try context!.save() + try context.save() Logger.data.info("💾 Updated Waypoint from Waypoint App Packet From: \(fromNodeNum.toHex(), privacy: .public)") } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("Error Saving NodeInfoEntity from WAYPOINT_APP \(nsError, privacy: .public)") } } return success } - + public func getPositionFromPhoneGPS(destNum: Int64) -> Position? { var positionPacket = Position() if #available(iOS 17.0, macOS 14.0, *) { - + guard let lastLocation = LocationsHandler.shared.locationsArray.last else { return nil } @@ -1131,7 +1139,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) positionPacket.altitude = Int32(lastLocation.altitude) positionPacket.satsInView = UInt32(LocationsHandler.satsInView) - + let currentSpeed = lastLocation.speed if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { positionPacket.groundSpeed = UInt32(currentSpeed) @@ -1140,9 +1148,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { positionPacket.groundTrack = UInt32(currentHeading) } - + } else { - + positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) let timestamp = LocationHelper.shared.locationManager.location?.timestamp ?? Date() @@ -1161,7 +1169,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return positionPacket } - + public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool { var adminPacket = AdminMessage() guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num) else { @@ -1190,7 +1198,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func removeFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.removeFixedPosition = true @@ -1215,14 +1223,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool { var success = false let fromNodeNum = connectedPeripheral.num guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum) else { return false } - + var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.channel = UInt32(channel) @@ -1236,7 +1244,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } else { return false } - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket @@ -1260,7 +1268,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } } - + public func sendShutdown(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.shutdownSeconds = 5 @@ -1285,7 +1293,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendReboot(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootSeconds = 5 @@ -1310,7 +1318,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendRebootOta(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootOtaSeconds = 5 @@ -1335,7 +1343,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendEnterDfuMode(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.enterDfuModeRequest = true @@ -1361,7 +1369,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.factoryReset = 5 @@ -1379,14 +1387,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } else { return false } - + let messageDescription = "🚀 Sent Factory Reset Admin Message to: \(toUser.longName ?? "unknown".localized) from: \(fromUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func sendNodeDBReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.nodedbReset = 5 @@ -1402,7 +1410,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage let messageDescription = "🚀 Sent NodeDB Reset Admin Message to: \(toUser.longName ?? "unknown".localized) from: \(fromUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { @@ -1410,7 +1418,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func connectToPreferredPeripheral() -> Bool { var success = false // Return false if we are not properly connected to a device, handle retry logic in the view for now @@ -1428,9 +1436,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return success } - + public func getChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.getChannelRequest = UInt32(channel.index + 1) var meshPacket: MeshPacket = MeshPacket() @@ -1446,7 +1454,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "🎛️ Requested Channel \(channel.index) for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return Int64(meshPacket.id) @@ -1454,7 +1462,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } public func saveChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setChannel = channel var meshPacket: MeshPacket = MeshPacket() @@ -1470,24 +1478,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Channel \(channel.index) for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return Int64(meshPacket.id) } return 0 } - + public func saveChannelSet(base64UrlString: String, addChannels: Bool = false) -> Bool { if isConnected { - + var i: Int32 = 0 var myInfo: MyInfoEntity // Before we get started delete the existing channels from the myNodeInfo if !addChannels { tryClearExistingChannels() } - + let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { @@ -1498,7 +1506,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let fetchMyInfoRequest = MyInfoEntity.fetchRequest() fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) do { - let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) ?? [] + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) if fetchedMyInfo.count == 1 { i = Int32(fetchedMyInfo[0].channels?.count ?? -1) myInfo = fetchedMyInfo[0] @@ -1518,7 +1526,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.data.error("Failed to find a node MyInfo to save these channels to: \(error.localizedDescription)") } } - + var chan = Channel() if i == 0 { chan.role = Channel.Role.primary @@ -1528,7 +1536,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate chan.settings = cs chan.index = i i += 1 - + var adminPacket = AdminMessage() adminPacket.setChannel = chan var meshPacket: MeshPacket = MeshPacket() @@ -1585,12 +1593,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let logString = String.localizedStringWithFormat("mesh.log.lora.config.sent %@".localized, String(connectedPeripheral.num)) MeshLogger.log("📻 \(logString)") } - + if self.connectedPeripheral != nil { self.sendWantConfig() return true } - + } catch { return false } @@ -1598,7 +1606,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setOwner = config @@ -1623,7 +1631,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return 0 } - + public func removeNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { var adminPacket = AdminMessage() adminPacket.removeByNodenum = UInt32(node.num) @@ -1646,23 +1654,23 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate guard let binaryData: Data = try? toRadio.serializedData() else { return false } - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - context!.delete(node.user!) - context!.delete(node) - try context!.save() + context.delete(node.user!) + context.delete(node) + try context.save() return true } catch { - context!.rollback() + context.rollback() let nsError = error as NSError Logger.data.error("🚫 Error deleting node from core data: \(nsError)") } } return false } - + public func setFavoriteNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { var adminPacket = AdminMessage() adminPacket.setFavoriteNode = UInt32(node.num) @@ -1684,14 +1692,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate guard let binaryData: Data = try? toRadio.serializedData() else { return false } - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true } return false } - + public func removeFavoriteNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { var adminPacket = AdminMessage() adminPacket.removeFavoriteNode = UInt32(node.num) @@ -1713,14 +1721,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate guard let binaryData: Data = try? toRadio.serializedData() else { return false } - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true } return false } - + public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setHamMode = ham @@ -1762,20 +1770,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Bluetooth Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func saveDeviceConfig(config: Config.DeviceConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.device = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1792,12 +1800,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Device Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertDeviceConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } return 0 } - + public func saveDisplayConfig(config: Config.DisplayConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.display = config @@ -1819,14 +1827,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Display Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertDisplayConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } return 0 } - + public func saveLoRaConfig(config: Config.LoRaConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.lora = config var meshPacket: MeshPacket = MeshPacket() @@ -1844,20 +1852,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved LoRa Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertLoRaConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func savePositionConfig(config: Config.PositionConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.position = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1871,24 +1879,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Position Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertPositionConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertPositionConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func savePowerConfig(config: Config.PowerConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.power = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1902,24 +1910,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Power Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertPowerConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertPowerConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func saveNetworkConfig(config: Config.NetworkConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.network = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1933,24 +1941,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Network Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertNetworkConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertNetworkConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func saveAmbientLightingModuleConfig(config: ModuleConfig.AmbientLightingConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.ambientLighting = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1965,22 +1973,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Ambient Lighting Module Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.cannedMessage = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1995,22 +2003,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Canned Message Module Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) + updateCoreDataController.upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num) return Int64(meshPacket.id) } - + return 0 } - + public func saveCannedMessageModuleMessages(messages: String, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setCannedMessageModuleMessages = messages - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2026,22 +2034,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Canned Message Module Messages for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - + return Int64(meshPacket.id) } - + return 0 } - + public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.detectionSensor = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.externalNotification = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2078,7 +2086,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.paxcounter = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2107,7 +2115,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setRingtoneMessage = ringtone - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.mqtt = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.rangeTest = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.serial = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.storeForward = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.telemetry = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - + var adminPacket = AdminMessage() adminPacket.getChannelRequest = channelIndex - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - + var adminPacket = AdminMessage() adminPacket.getCannedMessageModuleMessagesRequest = true - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.from = UInt32(connectedPeripheral.num) @@ -2346,7 +2354,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.wantAck = true meshPacket.decoded.wantResponse = wantResponse - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2354,32 +2362,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = wantResponse - + meshPacket.decoded = dataMessage - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket - + guard let binaryData: Data = try? toRadio.serializedData() else { return false } - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.get %@".localized, String(connectedPeripheral.num)) MeshLogger.log("🥫 \(logString)") return true } - + return false } - + public func requestBluetoothConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.bluetoothConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2387,7 +2395,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2395,22 +2403,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Bluetooth Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestDeviceConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2418,7 +2426,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2426,22 +2434,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Device Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestDisplayConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.displayConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2449,7 +2457,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2457,22 +2465,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Display Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestLoRaConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.loraConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2480,7 +2488,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2488,24 +2496,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested LoRa Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - + return true } - + return false } - + public func requestNetworkConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.networkConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2513,7 +2521,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2522,20 +2530,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Network Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestPositionConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.positionConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2543,7 +2551,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2551,21 +2559,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Position Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestPowerConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.powerConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2573,7 +2581,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2581,21 +2589,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Power Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestAmbientLightingConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.ambientlightingConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2603,7 +2611,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2611,21 +2619,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Ambient Lighting Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestCannedMessagesModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.cannedmsgConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2633,7 +2641,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2641,21 +2649,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Canned Messages Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestExternalNotificationModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.extnotifConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2663,7 +2671,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2671,21 +2679,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested External Notificaiton Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestPaxCounterModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.paxcounterConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2693,7 +2701,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2701,21 +2709,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested PAX Counter Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestRtttlConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getRingtoneRequest = true - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2723,7 +2731,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2731,21 +2739,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested RTTTL Ringtone Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestRangeTestModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.rangetestConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2753,7 +2761,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2761,21 +2769,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Range Test Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestMqttModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.mqttConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2783,7 +2791,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2791,21 +2799,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested MQTT Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestDetectionSensorModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2813,7 +2821,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2821,21 +2829,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Detection Sensor Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestSerialModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.serialConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2843,7 +2851,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2851,21 +2859,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Serial Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestStoreAndForwardModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.storeforwardConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2873,7 +2881,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2881,21 +2889,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Store and Forward Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + public func requestTelemetryModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.telemetryConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2903,7 +2911,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() guard let adminData: Data = try? adminPacket.serializedData() else { return false @@ -2911,26 +2919,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = adminData dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Telemetry Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { return true } return false } - + // Send an admin message to a radio, save a message to core data for logging private func sendAdminMessageToRadio(meshPacket: MeshPacket, adminDescription: String) -> Bool { - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket guard let binaryData: Data = try? toRadio.serializedData() else { return false } - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) Logger.mesh.debug("\(adminDescription)") @@ -2938,9 +2946,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func requestStoreAndForwardClientHistory(fromUser: UserEntity, toUser: UserEntity) -> Bool { - + /// send a request for ClientHistory with a time period matching the heartbeat var sfPacket = StoreAndForward() sfPacket.rr = StoreAndForward.RequestResponse.clientHistory @@ -2960,7 +2968,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.storeForwardApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket @@ -2974,7 +2982,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { // Handle each of the store and forward request / response messages @@ -2987,8 +2995,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate /// When we get a router heartbeat we know there is a store and forward node on the network /// Check if it is the primary S&F Router and save the timestamp of the last heartbeat so that we can show the request message history menu item on node long press if the router has been seen recently if storeAndForwardMessage.heartbeat.secondary == 0 { - - guard let routerNode = getNodeInfo(id: Int64(packet.from), context: context) else { + + guard let routerNode = queryCoreDataController.getNodeInfo(id: Int64(packet.from)) else { return } if routerNode.storeForwardConfig != nil { @@ -3002,7 +3010,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newConfig.lastHeartbeat = Date() routerNode.storeForwardConfig = newConfig } - + do { try context.save() } catch { @@ -3019,7 +3027,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🐝 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") case .routerHistory: /// Set the Router History Last Request Value - guard let routerNode = getNodeInfo(id: Int64(packet.from), context: context) else { + guard let routerNode = queryCoreDataController.getNodeInfo(id: Int64(packet.from)) else { return } if routerNode.storeForwardConfig != nil { @@ -3029,7 +3037,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newConfig.lastRequest = Int32(storeAndForwardMessage.history.lastRequest) routerNode.storeForwardConfig = newConfig } - + do { try context.save() } catch { @@ -3055,20 +3063,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") case .routerTextDirect: MeshLogger.log("💬 Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") - textMessageAppPacket(packet: packet, wantRangeTestPackets: false, connectedNode: connectedNodeNum, storeForward: true, context: context) + updateCoreDataController.textMessageAppPacket(packet: packet, wantRangeTestPackets: false, connectedNode: connectedNodeNum, storeForward: true) case .routerTextBroadcast: MeshLogger.log("✉️ Store and Forward \(storeAndForwardMessage.rr) message received from \(packet.from.toHex())") - textMessageAppPacket(packet: packet, wantRangeTestPackets: false, connectedNode: connectedNodeNum, storeForward: true, context: context) + updateCoreDataController.textMessageAppPacket(packet: packet, wantRangeTestPackets: false, connectedNode: connectedNodeNum, storeForward: true) } } } - + public func tryClearExistingChannels() { - guard let context else { return } // Before we get started delete the existing channels from the myNodeInfo let fetchMyInfoRequest = MyInfoEntity.fetchRequest() fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) - + do { let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) if fetchedMyInfo.count == 1 { @@ -3089,7 +3096,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MARK: - CB Central Manager implmentation extension BLEManager: CBCentralManagerDelegate { - + // MARK: Bluetooth enabled/disabled func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == CBManagerState.poweredOn { @@ -3099,9 +3106,9 @@ extension BLEManager: CBCentralManagerDelegate { } else { isSwitchedOn = false } - + var status = "" - + switch central.state { case .poweredOff: status = "BLE is powered off" @@ -3120,10 +3127,10 @@ extension BLEManager: CBCentralManagerDelegate { } Logger.services.info("📜 [BLE] Bluetooth status: \(status)") } - + // Called each time a peripheral is discovered func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { - + if self.automaticallyReconnect && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { self.connectTo(peripheral: peripheral) Logger.services.info("✅ [BLE] Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown", privacy: .public)") @@ -3131,7 +3138,7 @@ extension BLEManager: CBCentralManagerDelegate { let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "?", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral) let index = peripherals.map { $0.peripheral }.firstIndex(of: peripheral) - + if let peripheralIndex = index { peripherals[peripheralIndex] = device } else { diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 11da4145..fe9080c0 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -15,711 +15,713 @@ import OSLog import ActivityKit #endif -func generateMessageMarkdown (message: String) -> String { - if !message.isEmoji() { - let types: NSTextCheckingResult.CheckingType = [.address, .link, .phoneNumber] - guard let detector = try? NSDataDetector(types: types.rawValue) else { - return message - } - let matches = detector.matches(in: message, options: [], range: NSRange(location: 0, length: message.utf16.count)) - var messageWithMarkdown = message - if matches.count > 0 { - for match in matches { - guard let range = Range(match.range, in: message) else { continue } - if match.resultType == .address { - let address = message[range] - let urlEncodedAddress = address.addingPercentEncoding(withAllowedCharacters: .alphanumerics) - messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: address, with: "[\(address)](http://maps.apple.com/?address=\(urlEncodedAddress ?? ""))") - } else if match.resultType == .phoneNumber { - let phone = messageWithMarkdown[range] - messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: phone, with: "[\(phone)](tel:\(phone))") - } else if match.resultType == .link { - let start = match.range.lowerBound - let stop = match.range.upperBound - let url = message[start ..< stop] - let absoluteUrl = match.url?.absoluteString ?? "" - let markdownUrl = "[\(url)](\(absoluteUrl))" - messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: url, with: markdownUrl) +extension UpdateCoreDataController { + + func generateMessageMarkdown (message: String) -> String { + if !message.isEmoji() { + let types: NSTextCheckingResult.CheckingType = [.address, .link, .phoneNumber] + guard let detector = try? NSDataDetector(types: types.rawValue) else { + return message + } + let matches = detector.matches(in: message, options: [], range: NSRange(location: 0, length: message.utf16.count)) + var messageWithMarkdown = message + if matches.count > 0 { + for match in matches { + guard let range = Range(match.range, in: message) else { continue } + if match.resultType == .address { + let address = message[range] + let urlEncodedAddress = address.addingPercentEncoding(withAllowedCharacters: .alphanumerics) + messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: address, with: "[\(address)](http://maps.apple.com/?address=\(urlEncodedAddress ?? ""))") + } else if match.resultType == .phoneNumber { + let phone = messageWithMarkdown[range] + messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: phone, with: "[\(phone)](tel:\(phone))") + } else if match.resultType == .link { + let start = match.range.lowerBound + let stop = match.range.upperBound + let url = message[start ..< stop] + let absoluteUrl = match.url?.absoluteString ?? "" + let markdownUrl = "[\(url)](\(absoluteUrl))" + messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: url, with: markdownUrl) + } } } + return messageWithMarkdown } - return messageWithMarkdown + return message } - return message -} - -func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { - // We don't care about any of the Power settings, config is available for everything else - if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { - upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { - upsertDeviceConfigPacket(config: config.device, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { - upsertDisplayConfigPacket(config: config.display, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { - upsertLoRaConfigPacket(config: config.lora, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { - upsertNetworkConfigPacket(config: config.network, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { - upsertPositionConfigPacket(config: config.position, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { - upsertPowerConfigPacket(config: config.power, nodeNum: nodeNum, context: context) - } -} - -func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { - - if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.ambientLighting(config.ambientLighting) { - upsertAmbientLightingModuleConfigPacket(config: config.ambientLighting, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(config.cannedMessage) { - upsertCannedMessagesModuleConfigPacket(config: config.cannedMessage, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(config.detectionSensor) { - upsertDetectionSensorModuleConfigPacket(config: config.detectionSensor, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(config.externalNotification) { - upsertExternalNotificationModuleConfigPacket(config: config.externalNotification, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(config.mqtt) { - upsertMqttModuleConfigPacket(config: config.mqtt, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.paxcounter(config.paxcounter) { - upsertPaxCounterModuleConfigPacket(config: config.paxcounter, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(config.rangeTest) { - upsertRangeTestModuleConfigPacket(config: config.rangeTest, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(config.serial) { - upsertSerialModuleConfigPacket(config: config.serial, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(config.telemetry) { - upsertTelemetryModuleConfigPacket(config: config.telemetry, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.storeForward(config.storeForward) { - upsertStoreForwardModuleConfigPacket(config: config.storeForward, nodeNum: nodeNum, context: context) - } -} - -func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedObjectContext) -> MyInfoEntity? { - - let logString = String.localizedStringWithFormat("mesh.log.myinfo %@".localized, String(myInfo.myNodeNum)) - MeshLogger.log("ℹ️ \(logString)") - - let fetchMyInfoRequest = MyInfoEntity.fetchRequest() - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) - - do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) - // Not Found Insert - if fetchedMyInfo.isEmpty { - - let myInfoEntity = MyInfoEntity(context: context) - myInfoEntity.peripheralId = peripheralId - myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) - myInfoEntity.rebootCount = Int32(myInfo.rebootCount) - do { - try context.save() - Logger.data.info("💾 Saved a new myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)") - return myInfoEntity - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Inserting New Core Data MyInfoEntity: \(nsError, privacy: .public)") - } - } else { - - fetchedMyInfo[0].peripheralId = peripheralId - fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum) - fetchedMyInfo[0].rebootCount = Int32(myInfo.rebootCount) - - do { - try context.save() - Logger.data.info("💾 Updated myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)") - return fetchedMyInfo[0] - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Updating Core Data MyInfoEntity: \(nsError, privacy: .public)") - } + + func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { + // We don't care about any of the Power settings, config is available for everything else + if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { + upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { + upsertDeviceConfigPacket(config: config.device, nodeNum: nodeNum) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { + upsertDisplayConfigPacket(config: config.display, nodeNum: nodeNum) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { + upsertLoRaConfigPacket(config: config.lora, nodeNum: nodeNum) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { + upsertNetworkConfigPacket(config: config.network, nodeNum: nodeNum) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { + upsertPositionConfigPacket(config: config.position, nodeNum: nodeNum) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { + upsertPowerConfigPacket(config: config.power, nodeNum: nodeNum) } - } catch { - Logger.data.error("💥 Fetch MyInfo Error") } - return nil -} - -func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectContext) { - - if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled { - - let logString = String.localizedStringWithFormat("mesh.log.channel.received %d %@".localized, channel.index, String(fromNum)) - MeshLogger.log("🎛️ \(logString)") - - let fetchedMyInfoRequest = MyInfoEntity.fetchRequest() - fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum) - + + func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { + + if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.ambientLighting(config.ambientLighting) { + upsertAmbientLightingModuleConfigPacket(config: config.ambientLighting, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(config.cannedMessage) { + upsertCannedMessagesModuleConfigPacket(config: config.cannedMessage, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(config.detectionSensor) { + upsertDetectionSensorModuleConfigPacket(config: config.detectionSensor, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(config.externalNotification) { + upsertExternalNotificationModuleConfigPacket(config: config.externalNotification, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(config.mqtt) { + upsertMqttModuleConfigPacket(config: config.mqtt, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.paxcounter(config.paxcounter) { + upsertPaxCounterModuleConfigPacket(config: config.paxcounter, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(config.rangeTest) { + upsertRangeTestModuleConfigPacket(config: config.rangeTest, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(config.serial) { + upsertSerialModuleConfigPacket(config: config.serial, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(config.telemetry) { + upsertTelemetryModuleConfigPacket(config: config.telemetry, nodeNum: nodeNum) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.storeForward(config.storeForward) { + upsertStoreForwardModuleConfigPacket(config: config.storeForward, nodeNum: nodeNum) + } + } + + func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String) -> MyInfoEntity? { + + let logString = String.localizedStringWithFormat("mesh.log.myinfo %@".localized, String(myInfo.myNodeNum)) + MeshLogger.log("ℹ️ \(logString)") + + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) + do { - let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) - if fetchedMyInfo.count == 1 { - let newChannel = ChannelEntity(context: context) - newChannel.id = Int32(channel.index) - newChannel.index = Int32(channel.index) - newChannel.uplinkEnabled = channel.settings.uplinkEnabled - newChannel.downlinkEnabled = channel.settings.downlinkEnabled - newChannel.name = channel.settings.name - newChannel.role = Int32(channel.role.rawValue) - newChannel.psk = channel.settings.psk - if channel.settings.hasModuleSettings { - newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision) - newChannel.mute = channel.settings.moduleSettings.isClientMuted - } - guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else { - return - } - if let oldChannel = mutableChannels.first(where: {($0 as AnyObject).index == newChannel.index }) as? ChannelEntity { - let index = mutableChannels.index(of: oldChannel as Any) - mutableChannels.replaceObject(at: index, with: newChannel) - } else { - mutableChannels.add(newChannel) - } - fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet - if newChannel.name?.lowercased() == "admin" { - fetchedMyInfo[0].adminIndex = newChannel.index - } - context.refresh(newChannel, mergeChanges: true) + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + // Not Found Insert + if fetchedMyInfo.isEmpty { + + let myInfoEntity = MyInfoEntity(context: context) + myInfoEntity.peripheralId = peripheralId + myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) + myInfoEntity.rebootCount = Int32(myInfo.rebootCount) do { try context.save() - } catch { - Logger.data.error("💥 Failed to save channel: \(error.localizedDescription, privacy: .public)") - } - Logger.data.info("💾 Updated MyInfo channel \(channel.index) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)") - } else if channel.role.rawValue > 0 { - Logger.data.error("💥Trying to save a channel to a MyInfo that does not exist: \(fromNum.toHex(), privacy: .public)") - } - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError, privacy: .public)") - } - } -} - -func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NSManagedObjectContext) { - - if metadata.isInitialized { - let logString = String.localizedStringWithFormat("mesh.log.device.metadata.received %@".localized, fromNum.toHex()) - MeshLogger.log("🏷️ \(logString)") - - let fetchedNodeRequest = NodeInfoEntity.fetchRequest() - fetchedNodeRequest.predicate = NSPredicate(format: "num == %lld", fromNum) - - do { - let fetchedNode = try context.fetch(fetchedNodeRequest) - let newMetadata = DeviceMetadataEntity(context: context) - newMetadata.time = Date() - newMetadata.deviceStateVersion = Int32(metadata.deviceStateVersion) - newMetadata.canShutdown = metadata.canShutdown - newMetadata.hasWifi = metadata.hasWifi_p - newMetadata.hasBluetooth = metadata.hasBluetooth_p - newMetadata.hasEthernet = metadata.hasEthernet_p - newMetadata.role = Int32(metadata.role.rawValue) - newMetadata.positionFlags = Int32(metadata.positionFlags) - // Swift does strings weird, this does work to get the version without the github hash - let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".") - var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))] - version = version.dropLast() - newMetadata.firmwareVersion = String(version) - if fetchedNode.count > 0 { - fetchedNode[0].metadata = newMetadata - } else { - - if fromNum > 0 { - let newNode = createNodeInfo(num: Int64(fromNum), context: context) - newNode.metadata = newMetadata - } - } - do { - try context.save() - } catch { - Logger.data.error("💥 Failed to save device metadata: \(error.localizedDescription, privacy: .public)") - } - Logger.data.info("💾 Updated Device Metadata from Admin App Packet For: \(fromNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving MyInfo Channel from ADMIN_APP \(nsError, privacy: .public)") - } - } -} - -func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? { - - let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, String(nodeInfo.num)) - MeshLogger.log("📟 \(logString)") - - guard nodeInfo.num > 0 else { return nil } - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Not Found Insert - if fetchedNode.isEmpty && nodeInfo.num > 0 { - - let newNode = NodeInfoEntity(context: context) - newNode.id = Int64(nodeInfo.num) - newNode.num = Int64(nodeInfo.num) - newNode.channel = Int32(nodeInfo.channel) - newNode.favorite = nodeInfo.isFavorite - newNode.hopsAway = Int32(nodeInfo.hopsAway) - - if nodeInfo.hasDeviceMetrics { - let telemetry = TelemetryEntity(context: context) - telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) - telemetry.voltage = nodeInfo.deviceMetrics.voltage - telemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization - telemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx - var newTelemetries = [TelemetryEntity]() - newTelemetries.append(telemetry) - newNode.telemetries? = NSOrderedSet(array: newTelemetries) - } - newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) - newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) - newNode.snr = nodeInfo.snr - if nodeInfo.hasUser { - - let newUser = UserEntity(context: context) - newUser.userId = nodeInfo.user.id - newUser.num = Int64(nodeInfo.num) - newUser.longName = nodeInfo.user.longName - newUser.shortName = nodeInfo.user.shortName - newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() - newUser.isLicensed = nodeInfo.user.isLicensed - newUser.role = Int32(nodeInfo.user.role.rawValue) - newNode.user = newUser - } else if nodeInfo.num > Int16.max { - let newUser = createUser(num: Int64(nodeInfo.num), context: context) - newNode.user = newUser - } - - if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { - let position = PositionEntity(context: context) - position.latest = true - position.seqNo = Int32(nodeInfo.position.seqNumber) - position.latitudeI = nodeInfo.position.latitudeI - position.longitudeI = nodeInfo.position.longitudeI - position.altitude = nodeInfo.position.altitude - position.satsInView = Int32(nodeInfo.position.satsInView) - position.speed = Int32(nodeInfo.position.groundSpeed) - position.heading = Int32(nodeInfo.position.groundTrack) - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - var newPostions = [PositionEntity]() - newPostions.append(position) - newNode.positions? = NSOrderedSet(array: newPostions) - } - - // Look for a MyInfo - let fetchMyInfoRequest = MyInfoEntity.fetchRequest() - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) - - do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) - if fetchedMyInfo.count > 0 { - newNode.myInfo = fetchedMyInfo[0] - } - do { - try context.save() - Logger.data.info("💾 Saved a new Node Info For: \(String(nodeInfo.num))") - return newNode + Logger.data.info("💾 Saved a new myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)") + return myInfoEntity } catch { context.rollback() let nsError = error as NSError - Logger.data.error("Error Saving Core Data NodeInfoEntity: \(nsError)") + Logger.data.error("💥 Error Inserting New Core Data MyInfoEntity: \(nsError, privacy: .public)") + } + } else { + + fetchedMyInfo[0].peripheralId = peripheralId + fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum) + fetchedMyInfo[0].rebootCount = Int32(myInfo.rebootCount) + + do { + try context.save() + Logger.data.info("💾 Updated myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)") + return fetchedMyInfo[0] + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 Error Updating Core Data MyInfoEntity: \(nsError, privacy: .public)") + } + } + } catch { + Logger.data.error("💥 Fetch MyInfo Error") + } + return nil + } + + func channelPacket (channel: Channel, fromNum: Int64) { + + if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled { + + let logString = String.localizedStringWithFormat("mesh.log.channel.received %d %@".localized, channel.index, String(fromNum)) + MeshLogger.log("🎛️ \(logString)") + + let fetchedMyInfoRequest = MyInfoEntity.fetchRequest() + fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum) + + do { + let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) + if fetchedMyInfo.count == 1 { + let newChannel = ChannelEntity(context: context) + newChannel.id = Int32(channel.index) + newChannel.index = Int32(channel.index) + newChannel.uplinkEnabled = channel.settings.uplinkEnabled + newChannel.downlinkEnabled = channel.settings.downlinkEnabled + newChannel.name = channel.settings.name + newChannel.role = Int32(channel.role.rawValue) + newChannel.psk = channel.settings.psk + if channel.settings.hasModuleSettings { + newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision) + newChannel.mute = channel.settings.moduleSettings.isClientMuted + } + guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else { + return + } + if let oldChannel = mutableChannels.first(where: {($0 as AnyObject).index == newChannel.index }) as? ChannelEntity { + let index = mutableChannels.index(of: oldChannel as Any) + mutableChannels.replaceObject(at: index, with: newChannel) + } else { + mutableChannels.add(newChannel) + } + fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet + if newChannel.name?.lowercased() == "admin" { + fetchedMyInfo[0].adminIndex = newChannel.index + } + context.refresh(newChannel, mergeChanges: true) + do { + try context.save() + } catch { + Logger.data.error("💥 Failed to save channel: \(error.localizedDescription, privacy: .public)") + } + Logger.data.info("💾 Updated MyInfo channel \(channel.index) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)") + } else if channel.role.rawValue > 0 { + Logger.data.error("💥Trying to save a channel to a MyInfo that does not exist: \(fromNum.toHex(), privacy: .public)") } } catch { - Logger.data.error("Fetch MyInfo Error") + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError, privacy: .public)") } - } else if nodeInfo.num > 0 { - - fetchedNode[0].id = Int64(nodeInfo.num) - fetchedNode[0].num = Int64(nodeInfo.num) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) - fetchedNode[0].snr = nodeInfo.snr - fetchedNode[0].channel = Int32(nodeInfo.channel) - fetchedNode[0].favorite = nodeInfo.isFavorite - fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway) - - if nodeInfo.hasUser { - if fetchedNode[0].user == nil { - fetchedNode[0].user = UserEntity(context: context) + } + } + + func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64) { + + if metadata.isInitialized { + let logString = String.localizedStringWithFormat("mesh.log.device.metadata.received %@".localized, fromNum.toHex()) + MeshLogger.log("🏷️ \(logString)") + + let fetchedNodeRequest = NodeInfoEntity.fetchRequest() + fetchedNodeRequest.predicate = NSPredicate(format: "num == %lld", fromNum) + + do { + let fetchedNode = try context.fetch(fetchedNodeRequest) + let newMetadata = DeviceMetadataEntity(context: context) + newMetadata.time = Date() + newMetadata.deviceStateVersion = Int32(metadata.deviceStateVersion) + newMetadata.canShutdown = metadata.canShutdown + newMetadata.hasWifi = metadata.hasWifi_p + newMetadata.hasBluetooth = metadata.hasBluetooth_p + newMetadata.hasEthernet = metadata.hasEthernet_p + newMetadata.role = Int32(metadata.role.rawValue) + newMetadata.positionFlags = Int32(metadata.positionFlags) + // Swift does strings weird, this does work to get the version without the github hash + let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".") + var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))] + version = version.dropLast() + newMetadata.firmwareVersion = String(version) + if fetchedNode.count > 0 { + fetchedNode[0].metadata = newMetadata + } else { + + if fromNum > 0 { + let newNode = createNodeInfo(num: Int64(fromNum), context: context) + newNode.metadata = newMetadata + } } - fetchedNode[0].user!.userId = nodeInfo.user.id - fetchedNode[0].user!.num = Int64(nodeInfo.num) - fetchedNode[0].user!.numString = String(nodeInfo.num) - fetchedNode[0].user!.longName = nodeInfo.user.longName - fetchedNode[0].user!.shortName = nodeInfo.user.shortName - fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed - fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) - fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() - } else { - if fetchedNode[0].user == nil && nodeInfo.num > Int16.max { - + do { + try context.save() + } catch { + Logger.data.error("💥 Failed to save device metadata: \(error.localizedDescription, privacy: .public)") + } + Logger.data.info("💾 Updated Device Metadata from Admin App Packet For: \(fromNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving MyInfo Channel from ADMIN_APP \(nsError, privacy: .public)") + } + } + } + + func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32) -> NodeInfoEntity? { + + let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, String(nodeInfo.num)) + MeshLogger.log("📟 \(logString)") + + guard nodeInfo.num > 0 else { return nil } + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Not Found Insert + if fetchedNode.isEmpty && nodeInfo.num > 0 { + + let newNode = NodeInfoEntity(context: context) + newNode.id = Int64(nodeInfo.num) + newNode.num = Int64(nodeInfo.num) + newNode.channel = Int32(nodeInfo.channel) + newNode.favorite = nodeInfo.isFavorite + newNode.hopsAway = Int32(nodeInfo.hopsAway) + + if nodeInfo.hasDeviceMetrics { + let telemetry = TelemetryEntity(context: context) + telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) + telemetry.voltage = nodeInfo.deviceMetrics.voltage + telemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization + telemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx + var newTelemetries = [TelemetryEntity]() + newTelemetries.append(telemetry) + newNode.telemetries? = NSOrderedSet(array: newTelemetries) + } + newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) + newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) + newNode.snr = nodeInfo.snr + if nodeInfo.hasUser { + + let newUser = UserEntity(context: context) + newUser.userId = nodeInfo.user.id + newUser.num = Int64(nodeInfo.num) + newUser.longName = nodeInfo.user.longName + newUser.shortName = nodeInfo.user.shortName + newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + newUser.isLicensed = nodeInfo.user.isLicensed + newUser.role = Int32(nodeInfo.user.role.rawValue) + newNode.user = newUser + } else if nodeInfo.num > Int16.max { let newUser = createUser(num: Int64(nodeInfo.num), context: context) - fetchedNode[0].user = newUser + newNode.user = newUser } - } - - if nodeInfo.hasDeviceMetrics { - - let newTelemetry = TelemetryEntity(context: context) - newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) - newTelemetry.voltage = nodeInfo.deviceMetrics.voltage - newTelemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization - newTelemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx - guard let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as? NSMutableOrderedSet else { - return nil - } - fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet - } - - if nodeInfo.hasPosition { - + if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { - let position = PositionEntity(context: context) + position.latest = true + position.seqNo = Int32(nodeInfo.position.seqNumber) position.latitudeI = nodeInfo.position.latitudeI position.longitudeI = nodeInfo.position.longitudeI position.altitude = nodeInfo.position.altitude position.satsInView = Int32(nodeInfo.position.satsInView) + position.speed = Int32(nodeInfo.position.groundSpeed) + position.heading = Int32(nodeInfo.position.groundTrack) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { + var newPostions = [PositionEntity]() + newPostions.append(position) + newNode.positions? = NSOrderedSet(array: newPostions) + } + + // Look for a MyInfo + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) + + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + if fetchedMyInfo.count > 0 { + newNode.myInfo = fetchedMyInfo[0] + } + do { + try context.save() + Logger.data.info("💾 Saved a new Node Info For: \(String(nodeInfo.num))") + return newNode + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving Core Data NodeInfoEntity: \(nsError)") + } + } catch { + Logger.data.error("Fetch MyInfo Error") + } + } else if nodeInfo.num > 0 { + + fetchedNode[0].id = Int64(nodeInfo.num) + fetchedNode[0].num = Int64(nodeInfo.num) + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) + fetchedNode[0].snr = nodeInfo.snr + fetchedNode[0].channel = Int32(nodeInfo.channel) + fetchedNode[0].favorite = nodeInfo.isFavorite + fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway) + + if nodeInfo.hasUser { + if fetchedNode[0].user == nil { + fetchedNode[0].user = UserEntity(context: context) + } + fetchedNode[0].user!.userId = nodeInfo.user.id + fetchedNode[0].user!.num = Int64(nodeInfo.num) + fetchedNode[0].user!.numString = String(nodeInfo.num) + fetchedNode[0].user!.longName = nodeInfo.user.longName + fetchedNode[0].user!.shortName = nodeInfo.user.shortName + fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed + fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) + fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + } else { + if fetchedNode[0].user == nil && nodeInfo.num > Int16.max { + + let newUser = createUser(num: Int64(nodeInfo.num), context: context) + fetchedNode[0].user = newUser + } + } + + if nodeInfo.hasDeviceMetrics { + + let newTelemetry = TelemetryEntity(context: context) + newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) + newTelemetry.voltage = nodeInfo.deviceMetrics.voltage + newTelemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization + newTelemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx + guard let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as? NSMutableOrderedSet else { return nil } - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet } - - } - - // Look for a MyInfo - let fetchMyInfoRequest = MyInfoEntity.fetchRequest() - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) - - do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) - if fetchedMyInfo.count > 0 { - fetchedNode[0].myInfo = fetchedMyInfo[0] - } - do { - try context.save() - Logger.data.info("💾 [NodeInfo] saved for \(nodeInfo.num.toHex(), privacy: .public)") - return fetchedNode[0] - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Saving Core Data NodeInfoEntity: \(nsError, privacy: .public)") - } - } catch { - Logger.data.error("💥 Fetch MyInfo Error") - } - } - } catch { - Logger.data.error("💥 Fetch NodeInfoEntity Error") - } - return nil -} - -func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) { - - if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) { - - if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { - - if !cmmc.messages.isEmpty { - - let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.received %@".localized, packet.from.toHex()) - MeshLogger.log("🥫 \(logString)") - - let fetchNodeRequest = NodeInfoEntity.fetchRequest() - fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - let fetchedNode = try context.fetch(fetchNodeRequest) - if fetchedNode.count == 1 { - let messages = String(cmmc.textFormatString()) - .replacingOccurrences(of: "11: ", with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) - fetchedNode[0].cannedMessageConfig?.messages = messages - do { - try context.save() - Logger.data.info("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError, privacy: .public)") - } - } - } catch { - Logger.data.error("💥 Error Deserializing ADMIN_APP packet.") - } - } - } - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) { - channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context) - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) { - deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), context: context) - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) { - let config = adminMessage.getConfigResponse - if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { - upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { - upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { - upsertDisplayConfigPacket(config: config.display, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { - upsertLoRaConfigPacket(config: config.lora, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { - upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { - upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { - upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from), context: context) - } - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) { - let moduleConfig = adminMessage.getModuleConfigResponse - if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.ambientLighting(moduleConfig.ambientLighting) { - upsertAmbientLightingModuleConfigPacket(config: moduleConfig.ambientLighting, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(moduleConfig.cannedMessage) { - upsertCannedMessagesModuleConfigPacket(config: moduleConfig.cannedMessage, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(moduleConfig.detectionSensor) { - upsertDetectionSensorModuleConfigPacket(config: moduleConfig.detectionSensor, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(moduleConfig.externalNotification) { - upsertExternalNotificationModuleConfigPacket(config: moduleConfig.externalNotification, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(moduleConfig.mqtt) { - upsertMqttModuleConfigPacket(config: moduleConfig.mqtt, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(moduleConfig.rangeTest) { - upsertRangeTestModuleConfigPacket(config: moduleConfig.rangeTest, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(moduleConfig.serial) { - upsertSerialModuleConfigPacket(config: moduleConfig.serial, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.storeForward(moduleConfig.storeForward) { - upsertStoreForwardModuleConfigPacket(config: moduleConfig.storeForward, nodeNum: Int64(packet.from), context: context) - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) { - upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context) - } - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getRingtoneResponse(adminMessage.getRingtoneResponse) { - let ringtone = adminMessage.getRingtoneResponse - upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: Int64(packet.from), context: context) - } else { - MeshLogger.log("🕸️ MESH PACKET received Admin App UNHANDLED \((try? packet.decoded.jsonString()) ?? "JSON Decode Failure")") - } - // Save an ack for the admin message log for each admin message response received as we stopped sending acks if there is also a response to reduce airtime. - adminResponseAck(packet: packet, context: context) - } -} - -func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) { - - let fetchedAdminMessageRequest = MessageEntity.fetchRequest() - fetchedAdminMessageRequest.predicate = NSPredicate(format: "messageId == %lld", packet.decoded.requestID) - do { - let fetchedMessage = try context.fetch(fetchedAdminMessageRequest) - if fetchedMessage.count > 0 { - fetchedMessage[0].ackTimestamp = Int32(Date().timeIntervalSince1970) - fetchedMessage[0].ackError = Int32(RoutingError.none.rawValue) - fetchedMessage[0].receivedACK = true - fetchedMessage[0].realACK = true - fetchedMessage[0].ackSNR = packet.rxSnr - if fetchedMessage[0].fromUser != nil { - fetchedMessage[0].fromUser?.objectWillChange.send() - } - do { - try context.save() - } catch { - Logger.data.error("Failed to save admin message response as an ack: \(error.localizedDescription)") - } - } - } catch { - Logger.data.error("Failed to fetch admin message by requestID: \(error.localizedDescription)") - } -} -func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.paxcounter %@".localized, String(packet.from)) - MeshLogger.log("🧑‍🤝‍🧑 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - - if let paxMessage = try? Paxcount(serializedData: packet.decoded.payload) { - - let newPax = PaxCounterEntity(context: context) - newPax.ble = Int32(truncatingIfNeeded: paxMessage.ble) - newPax.wifi = Int32(truncatingIfNeeded: paxMessage.wifi) - newPax.uptime = Int32(truncatingIfNeeded: paxMessage.uptime) - newPax.time = Date() - - if fetchedNode.count > 0 { - guard let mutablePax = fetchedNode[0].pax!.mutableCopy() as? NSMutableOrderedSet else { - return - } - mutablePax.add(newPax) - fetchedNode[0].pax = mutablePax - do { - try context.save() - } catch { - Logger.data.error("Failed to save pax: \(error.localizedDescription)") - } - } else { - Logger.data.info("Node Info Not Found") - } - } - } catch { - - } -} - -func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { - - if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { - - let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue) - - let routingErrorString = routingError?.display ?? "unknown".localized - let logString = String.localizedStringWithFormat("mesh.log.routing.message %@ %@".localized, String(packet.decoded.requestID), routingErrorString) - MeshLogger.log("🕸️ \(logString)") - - let fetchMessageRequest = MessageEntity.fetchRequest() - fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID)) - - do { - let fetchedMessage = try context.fetch(fetchMessageRequest) - if fetchedMessage.count > 0 { - - if fetchedMessage[0].toUser != nil { - // Real ACK from DM Recipient - if packet.to != packet.from { - fetchedMessage[0].realACK = true - } - } - fetchedMessage[0].ackError = Int32(routingMessage.errorReason.rawValue) - - if routingMessage.errorReason == Routing.Error.none { - - fetchedMessage[0].receivedACK = true - } - fetchedMessage[0].ackSNR = packet.rxSnr - fetchedMessage[0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime) - - if fetchedMessage[0].toUser != nil { - fetchedMessage[0].toUser!.objectWillChange.send() - } else { - let fetchMyInfoRequest = MyInfoEntity.fetchRequest() - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", connectedNodeNum) - do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) - if fetchedMyInfo.count > 0 { - - for ch in fetchedMyInfo[0].channels!.array as? [ChannelEntity] ?? [] where ch.index == packet.channel { - ch.objectWillChange.send() - } - } - } catch { } - } - - } else { - return - } - try context.save() - Logger.data.info("💾 ACK Saved for Message: \(packet.decoded.requestID)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving ACK for message: \(packet.id) Error: \(nsError)") - } - } -} - -func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) { - - if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { - - // Only log telemetry from the mesh not the connected device - if connectedNode != Int64(packet.from) { - let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from)) - MeshLogger.log("📈 \(logString)") - } else { - // If it is the connected node - } - - let telemetry = TelemetryEntity(context: context) - - let fetchNodeTelemetryRequest = NodeInfoEntity.fetchRequest() - fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) - if fetchedNode.count == 1 { - if telemetryMessage.variant == Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) { - // Device Metrics - telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx - telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization - telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel) - telemetry.voltage = telemetryMessage.deviceMetrics.voltage - telemetry.uptimeSeconds = Int32(telemetryMessage.deviceMetrics.uptimeSeconds) - telemetry.metricsType = 0 - Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.deviceMetrics.channelUtilization) Airtime: \(telemetryMessage.deviceMetrics.airUtilTx) for Node: \(packet.from.toHex())") - } else if telemetryMessage.variant == Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) { - // Environment Metrics - telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure - telemetry.current = telemetryMessage.environmentMetrics.current - telemetry.iaq = Int32(truncatingIfNeeded: telemetryMessage.environmentMetrics.iaq) - telemetry.gasResistance = telemetryMessage.environmentMetrics.gasResistance - telemetry.relativeHumidity = telemetryMessage.environmentMetrics.relativeHumidity - telemetry.temperature = telemetryMessage.environmentMetrics.temperature - telemetry.current = telemetryMessage.environmentMetrics.current - telemetry.voltage = telemetryMessage.environmentMetrics.voltage - telemetry.metricsType = 1 - } - telemetry.snr = packet.rxSnr - telemetry.rssi = packet.rxRssi - telemetry.time = Date(timeIntervalSince1970: TimeInterval(Int64(truncatingIfNeeded: telemetryMessage.time))) - guard let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as? NSMutableOrderedSet else { + if nodeInfo.hasPosition { + + if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { + + let position = PositionEntity(context: context) + position.latitudeI = nodeInfo.position.latitudeI + position.longitudeI = nodeInfo.position.longitudeI + position.altitude = nodeInfo.position.altitude + position.satsInView = Int32(nodeInfo.position.satsInView) + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) + guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { + return nil + } + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + } + + } + + // Look for a MyInfo + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) + + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + if fetchedMyInfo.count > 0 { + fetchedNode[0].myInfo = fetchedMyInfo[0] + } + do { + try context.save() + Logger.data.info("💾 [NodeInfo] saved for \(nodeInfo.num.toHex(), privacy: .public)") + return fetchedNode[0] + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 Error Saving Core Data NodeInfoEntity: \(nsError, privacy: .public)") + } + } catch { + Logger.data.error("💥 Fetch MyInfo Error") + } + } + } catch { + Logger.data.error("💥 Fetch NodeInfoEntity Error") + } + return nil + } + + func adminAppPacket (packet: MeshPacket) { + + if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) { + + if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) { + + if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { + + if !cmmc.messages.isEmpty { + + let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.received %@".localized, packet.from.toHex()) + MeshLogger.log("🥫 \(logString)") + + let fetchNodeRequest = NodeInfoEntity.fetchRequest() + fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + let fetchedNode = try context.fetch(fetchNodeRequest) + if fetchedNode.count == 1 { + let messages = String(cmmc.textFormatString()) + .replacingOccurrences(of: "11: ", with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + fetchedNode[0].cannedMessageConfig?.messages = messages + do { + try context.save() + Logger.data.info("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError, privacy: .public)") + } + } + } catch { + Logger.data.error("💥 Error Deserializing ADMIN_APP packet.") + } + } + } + } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) { + channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from)) + } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) { + deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from)) + } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) { + let config = adminMessage.getConfigResponse + if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { + upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from)) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { + upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from)) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { + upsertDisplayConfigPacket(config: config.display, nodeNum: Int64(packet.from)) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { + upsertLoRaConfigPacket(config: config.lora, nodeNum: Int64(packet.from)) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { + upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from)) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { + upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from)) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { + upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from)) + } + } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) { + let moduleConfig = adminMessage.getModuleConfigResponse + if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.ambientLighting(moduleConfig.ambientLighting) { + upsertAmbientLightingModuleConfigPacket(config: moduleConfig.ambientLighting, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(moduleConfig.cannedMessage) { + upsertCannedMessagesModuleConfigPacket(config: moduleConfig.cannedMessage, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(moduleConfig.detectionSensor) { + upsertDetectionSensorModuleConfigPacket(config: moduleConfig.detectionSensor, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(moduleConfig.externalNotification) { + upsertExternalNotificationModuleConfigPacket(config: moduleConfig.externalNotification, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(moduleConfig.mqtt) { + upsertMqttModuleConfigPacket(config: moduleConfig.mqtt, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(moduleConfig.rangeTest) { + upsertRangeTestModuleConfigPacket(config: moduleConfig.rangeTest, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(moduleConfig.serial) { + upsertSerialModuleConfigPacket(config: moduleConfig.serial, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.storeForward(moduleConfig.storeForward) { + upsertStoreForwardModuleConfigPacket(config: moduleConfig.storeForward, nodeNum: Int64(packet.from)) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) { + upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from)) + } + } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getRingtoneResponse(adminMessage.getRingtoneResponse) { + let ringtone = adminMessage.getRingtoneResponse + upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: Int64(packet.from)) + } else { + MeshLogger.log("🕸️ MESH PACKET received Admin App UNHANDLED \((try? packet.decoded.jsonString()) ?? "JSON Decode Failure")") + } + // Save an ack for the admin message log for each admin message response received as we stopped sending acks if there is also a response to reduce airtime. + adminResponseAck(packet: packet) + } + } + + func adminResponseAck (packet: MeshPacket) { + + let fetchedAdminMessageRequest = MessageEntity.fetchRequest() + fetchedAdminMessageRequest.predicate = NSPredicate(format: "messageId == %lld", packet.decoded.requestID) + do { + let fetchedMessage = try context.fetch(fetchedAdminMessageRequest) + if fetchedMessage.count > 0 { + fetchedMessage[0].ackTimestamp = Int32(Date().timeIntervalSince1970) + fetchedMessage[0].ackError = Int32(RoutingError.none.rawValue) + fetchedMessage[0].receivedACK = true + fetchedMessage[0].realACK = true + fetchedMessage[0].ackSNR = packet.rxSnr + if fetchedMessage[0].fromUser != nil { + fetchedMessage[0].fromUser?.objectWillChange.send() + } + do { + try context.save() + } catch { + Logger.data.error("Failed to save admin message response as an ack: \(error.localizedDescription)") + } + } + } catch { + Logger.data.error("Failed to fetch admin message by requestID: \(error.localizedDescription)") + } + } + func paxCounterPacket (packet: MeshPacket) { + + let logString = String.localizedStringWithFormat("mesh.log.paxcounter %@".localized, String(packet.from)) + MeshLogger.log("🧑‍🤝‍🧑 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + + if let paxMessage = try? Paxcount(serializedData: packet.decoded.payload) { + + let newPax = PaxCounterEntity(context: context) + newPax.ble = Int32(truncatingIfNeeded: paxMessage.ble) + newPax.wifi = Int32(truncatingIfNeeded: paxMessage.wifi) + newPax.uptime = Int32(truncatingIfNeeded: paxMessage.uptime) + newPax.time = Date() + + if fetchedNode.count > 0 { + guard let mutablePax = fetchedNode[0].pax!.mutableCopy() as? NSMutableOrderedSet else { + return + } + mutablePax.add(newPax) + fetchedNode[0].pax = mutablePax + do { + try context.save() + } catch { + Logger.data.error("Failed to save pax: \(error.localizedDescription)") + } + } else { + Logger.data.info("Node Info Not Found") + } + } + } catch { + + } + } + + func routingPacket (packet: MeshPacket, connectedNodeNum: Int64) { + + if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { + + let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue) + + let routingErrorString = routingError?.display ?? "unknown".localized + let logString = String.localizedStringWithFormat("mesh.log.routing.message %@ %@".localized, String(packet.decoded.requestID), routingErrorString) + MeshLogger.log("🕸️ \(logString)") + + let fetchMessageRequest = MessageEntity.fetchRequest() + fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID)) + + do { + let fetchedMessage = try context.fetch(fetchMessageRequest) + if fetchedMessage.count > 0 { + + if fetchedMessage[0].toUser != nil { + // Real ACK from DM Recipient + if packet.to != packet.from { + fetchedMessage[0].realACK = true + } + } + fetchedMessage[0].ackError = Int32(routingMessage.errorReason.rawValue) + + if routingMessage.errorReason == Routing.Error.none { + + fetchedMessage[0].receivedACK = true + } + fetchedMessage[0].ackSNR = packet.rxSnr + fetchedMessage[0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime) + + if fetchedMessage[0].toUser != nil { + fetchedMessage[0].toUser!.objectWillChange.send() + } else { + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", connectedNodeNum) + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + if fetchedMyInfo.count > 0 { + + for ch in fetchedMyInfo[0].channels!.array as? [ChannelEntity] ?? [] where ch.index == packet.channel { + ch.objectWillChange.send() + } + } + } catch { } + } + + } else { return } - mutableTelemetries.add(telemetry) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(truncatingIfNeeded: packet.rxTime))) - fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet + try context.save() + Logger.data.info("💾 ACK Saved for Message: \(packet.decoded.requestID)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving ACK for message: \(packet.id) Error: \(nsError)") } - try context.save() + } + } + + func telemetryPacket(packet: MeshPacket, connectedNode: Int64) { + + if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { + // Only log telemetry from the mesh not the connected device if connectedNode != Int64(packet.from) { - Logger.data.info("💾 [TelemetryEntity] Saved for Node: \(packet.from.toHex())") - } else if telemetry.metricsType == 0 { - // Connected Device Metrics - // ------------------------ - // Low Battery notification - if UserDefaults.lowBatteryNotifications && telemetry.batteryLevel > 0 && telemetry.batteryLevel < 4 { - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(UUID().uuidString)"), - title: "Critically Low Battery!", - subtitle: "AKA \(telemetry.nodeTelemetry?.user?.shortName ?? "UNK")", - content: "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining.", - target: "nodes", - path: "meshtastic://nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)" - ) - ] - manager.schedule() + let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from)) + MeshLogger.log("📈 \(logString)") + } else { + // If it is the connected node + } + + let telemetry = TelemetryEntity(context: context) + + let fetchNodeTelemetryRequest = NodeInfoEntity.fetchRequest() + fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) + if fetchedNode.count == 1 { + if telemetryMessage.variant == Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) { + // Device Metrics + telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx + telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization + telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel) + telemetry.voltage = telemetryMessage.deviceMetrics.voltage + telemetry.uptimeSeconds = Int32(telemetryMessage.deviceMetrics.uptimeSeconds) + telemetry.metricsType = 0 + Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.deviceMetrics.channelUtilization) Airtime: \(telemetryMessage.deviceMetrics.airUtilTx) for Node: \(packet.from.toHex())") + } else if telemetryMessage.variant == Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) { + // Environment Metrics + telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure + telemetry.current = telemetryMessage.environmentMetrics.current + telemetry.iaq = Int32(truncatingIfNeeded: telemetryMessage.environmentMetrics.iaq) + telemetry.gasResistance = telemetryMessage.environmentMetrics.gasResistance + telemetry.relativeHumidity = telemetryMessage.environmentMetrics.relativeHumidity + telemetry.temperature = telemetryMessage.environmentMetrics.temperature + telemetry.current = telemetryMessage.environmentMetrics.current + telemetry.voltage = telemetryMessage.environmentMetrics.voltage + telemetry.metricsType = 1 + } + telemetry.snr = packet.rxSnr + telemetry.rssi = packet.rxRssi + telemetry.time = Date(timeIntervalSince1970: TimeInterval(Int64(truncatingIfNeeded: telemetryMessage.time))) + + guard let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as? NSMutableOrderedSet else { + return + } + mutableTelemetries.add(telemetry) + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(truncatingIfNeeded: packet.rxTime))) + fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet } - // Update our live activity if there is one running, not available on mac iOS >= 16.2 + try context.save() + // Only log telemetry from the mesh not the connected device + if connectedNode != Int64(packet.from) { + Logger.data.info("💾 [TelemetryEntity] Saved for Node: \(packet.from.toHex())") + } else if telemetry.metricsType == 0 { + // Connected Device Metrics + // ------------------------ + // Low Battery notification + if UserDefaults.lowBatteryNotifications && telemetry.batteryLevel > 0 && telemetry.batteryLevel < 4 { + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(UUID().uuidString)"), + title: "Critically Low Battery!", + subtitle: "AKA \(telemetry.nodeTelemetry?.user?.shortName ?? "UNK")", + content: "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining.", + target: "nodes", + path: "meshtastic://nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)" + ) + ] + manager.schedule() + } + // Update our live activity if there is one running, not available on mac iOS >= 16.2 #if !targetEnvironment(macCatalyst) - + let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())! let date = Date.now...oneMinuteLater - let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel), nodes: 17, nodesOnline: 9) - let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Device Metrics Data.", sound: .default) + let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel), nodes: 17, nodesOnline: 9) + let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Device Metrics Data.", sound: .default) let updatedContent = ActivityContent(state: updatedMeshStatus, staleDate: nil) - + let meshActivity = Activity.activities.first(where: { $0.attributes.nodeNum == connectedNode }) if meshActivity != nil { Task { @@ -729,246 +731,247 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } } #endif - } - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Saving Telemetry for Node \(packet.from, privacy: .public) Error: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 Error Fetching NodeInfoEntity for Node \(packet.from.toHex(), privacy: .public)") - } -} - -func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) { - - var messageText = String(bytes: packet.decoded.payload, encoding: .utf8) - let rangeRef = Reference(Int.self) - let rangeTestRegex = Regex { - "seq " - - TryCapture(as: rangeRef) { - OneOrMore(.digit) - } transform: { match in - Int(match) - } - } - let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false - - if !wantRangeTestPackets && rangeTest { - return - } - var storeForwardBroadcast = false - if storeForward { - if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { - messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8) - if storeAndForwardMessage.rr == .routerTextBroadcast { - storeForwardBroadcast = true - } - } - } - - if messageText?.count ?? 0 > 0 { - - MeshLogger.log("💬 \("mesh.log.textmessage.received".localized)") - - let messageUsers = UserEntity.fetchRequest() - messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from]) - do { - let fetchedUsers = try context.fetch(messageUsers) - let newMessage = MessageEntity(context: context) - newMessage.messageId = Int64(packet.id) - newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) - newMessage.receivedTimestamp = Int32(Date().timeIntervalSince1970) - newMessage.receivedACK = false - newMessage.snr = packet.rxSnr - newMessage.rssi = packet.rxRssi - newMessage.isEmoji = packet.decoded.emoji == 1 - newMessage.channel = Int32(packet.channel) - newMessage.portNum = Int32(packet.decoded.portnum.rawValue) - if packet.decoded.portnum == PortNum.detectionSensorApp { - if !UserDefaults.enableDetectionNotifications { - newMessage.read = true - } - } - if packet.decoded.replyID > 0 { - newMessage.replyID = Int64(packet.decoded.replyID) - } - - if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != 4294967295 { - if !storeForwardBroadcast { - newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to }) - } - } - if fetchedUsers.first(where: { $0.num == packet.from }) != nil { - newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from }) - } - newMessage.messagePayload = messageText - newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText!) - if packet.to != 4294967295 && newMessage.fromUser != nil { - newMessage.fromUser?.lastMessage = Date() - } - var messageSaved = false - - do { - - try context.save() - Logger.data.info("💾 Saved a new message for \(newMessage.messageId)") - messageSaved = true - - if messageSaved { - - if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications { - return - } - let appState = AppState.shared - if newMessage.fromUser != nil && newMessage.toUser != nil { - // Set Unread Message Indicators - if packet.to == connectedNode { - appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - } - if !(newMessage.fromUser?.mute ?? false) { - // Create an iOS Notification for the received DM message and schedule it immediately - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", - subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", - content: messageText!, - target: "messages", - path: "meshtastic://messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)" - ) - ] - manager.schedule() - Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") - } - } else if newMessage.fromUser != nil && newMessage.toUser == nil { - - let fetchMyInfoRequest = MyInfoEntity.fetchRequest() - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) - - do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) - if !fetchedMyInfo.isEmpty { - appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - - for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { - if channel.index == newMessage.channel { - context.refresh(channel, mergeChanges: true) - } - if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications { - // Create an iOS Notification for the received private channel message and schedule it immediately - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", - subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", - content: messageText!, - target: "messages", - path: "meshtastic://messages?channel=\(newMessage.channel)&messageId=\(newMessage.messageId)") - ] - manager.schedule() - Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") - } - } - } - } catch { - - } - } } } catch { context.rollback() let nsError = error as NSError - Logger.data.error("Failed to save new MessageEntity \(nsError)") + Logger.data.error("💥 Error Saving Telemetry for Node \(packet.from, privacy: .public) Error: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 Error Fetching NodeInfoEntity for Node \(packet.from.toHex(), privacy: .public)") + } + } + + func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false) { + + var messageText = String(bytes: packet.decoded.payload, encoding: .utf8) + let rangeRef = Reference(Int.self) + let rangeTestRegex = Regex { + "seq " + + TryCapture(as: rangeRef) { + OneOrMore(.digit) + } transform: { match in + Int(match) + } + } + let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false + + if !wantRangeTestPackets && rangeTest { + return + } + var storeForwardBroadcast = false + if storeForward { + if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { + messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8) + if storeAndForwardMessage.rr == .routerTextBroadcast { + storeForwardBroadcast = true + } + } + } + + if messageText?.count ?? 0 > 0 { + + MeshLogger.log("💬 \("mesh.log.textmessage.received".localized)") + + let messageUsers = UserEntity.fetchRequest() + messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from]) + do { + let fetchedUsers = try context.fetch(messageUsers) + let newMessage = MessageEntity(context: context) + newMessage.messageId = Int64(packet.id) + newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) + newMessage.receivedTimestamp = Int32(Date().timeIntervalSince1970) + newMessage.receivedACK = false + newMessage.snr = packet.rxSnr + newMessage.rssi = packet.rxRssi + newMessage.isEmoji = packet.decoded.emoji == 1 + newMessage.channel = Int32(packet.channel) + newMessage.portNum = Int32(packet.decoded.portnum.rawValue) + if packet.decoded.portnum == PortNum.detectionSensorApp { + if !UserDefaults.enableDetectionNotifications { + newMessage.read = true + } + } + if packet.decoded.replyID > 0 { + newMessage.replyID = Int64(packet.decoded.replyID) + } + + if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != 4294967295 { + if !storeForwardBroadcast { + newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to }) + } + } + if fetchedUsers.first(where: { $0.num == packet.from }) != nil { + newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from }) + } + newMessage.messagePayload = messageText + newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText!) + if packet.to != 4294967295 && newMessage.fromUser != nil { + newMessage.fromUser?.lastMessage = Date() + } + var messageSaved = false + + do { + + try context.save() + Logger.data.info("💾 Saved a new message for \(newMessage.messageId)") + messageSaved = true + + if messageSaved { + + if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications { + return + } + let appState = AppState.shared + if newMessage.fromUser != nil && newMessage.toUser != nil { + // Set Unread Message Indicators + if packet.to == connectedNode { + appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 + UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages + } + if !(newMessage.fromUser?.mute ?? false) { + // Create an iOS Notification for the received DM message and schedule it immediately + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(newMessage.messageId)"), + title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", + subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", + content: messageText!, + target: "messages", + path: "meshtastic://messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)" + ) + ] + manager.schedule() + Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") + } + } else if newMessage.fromUser != nil && newMessage.toUser == nil { + + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) + + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + if !fetchedMyInfo.isEmpty { + appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages + UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages + + for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { + if channel.index == newMessage.channel { + context.refresh(channel, mergeChanges: true) + } + if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications { + // Create an iOS Notification for the received private channel message and schedule it immediately + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(newMessage.messageId)"), + title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", + subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", + content: messageText!, + target: "messages", + path: "meshtastic://messages?channel=\(newMessage.channel)&messageId=\(newMessage.messageId)") + ] + manager.schedule() + Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") + } + } + } + } catch { + + } + } + } + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Failed to save new MessageEntity \(nsError)") + } + } catch { + Logger.data.error("Fetch Message To and From Users Error") + } + } + } + + func waypointPacket (packet: MeshPacket) { + + let logString = String.localizedStringWithFormat("mesh.log.waypoint.received %@".localized, String(packet.from)) + MeshLogger.log("📍 \(logString)") + + let fetchWaypointRequest = WaypointEntity.fetchRequest() + fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id)) + + do { + + if let waypointMessage = try? Waypoint(serializedData: packet.decoded.payload) { + let fetchedWaypoint = try context.fetch(fetchWaypointRequest) + if fetchedWaypoint.isEmpty { + let waypoint = WaypointEntity(context: context) + + waypoint.id = Int64(packet.id) + waypoint.name = waypointMessage.name + waypoint.longDescription = waypointMessage.description_p + waypoint.latitudeI = waypointMessage.latitudeI + waypoint.longitudeI = waypointMessage.longitudeI + waypoint.icon = Int64(waypointMessage.icon) + waypoint.locked = Int64(waypointMessage.lockedTo) + if waypointMessage.expire >= 1 { + waypoint.expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire))) + } else { + waypoint.expire = nil + } + waypoint.created = Date() + do { + try context.save() + Logger.data.info("💾 Added Node Waypoint App Packet For: \(waypoint.id)") + let manager = LocalNotificationManager() + let icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") + let latitude = Double(waypoint.latitudeI) / 1e7 + let longitude = Double(waypoint.longitudeI) / 1e7 + manager.notifications = [ + Notification( + id: ("notification.id.\(waypoint.id)"), + title: "New Waypoint Received", + subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", + content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", + target: "map", + path: "meshtastic://map?waypontid=\(waypoint.id)" + ) + ] + Logger.data.debug("meshtastic://map?waypontid=\(waypoint.id)") + manager.schedule() + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError)") + } + } else { + fetchedWaypoint[0].id = Int64(packet.id) + fetchedWaypoint[0].name = waypointMessage.name + fetchedWaypoint[0].longDescription = waypointMessage.description_p + fetchedWaypoint[0].latitudeI = waypointMessage.latitudeI + fetchedWaypoint[0].longitudeI = waypointMessage.longitudeI + fetchedWaypoint[0].icon = Int64(waypointMessage.icon) + fetchedWaypoint[0].locked = Int64(waypointMessage.lockedTo) + if waypointMessage.expire >= 1 { + fetchedWaypoint[0].expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire))) + } else { + fetchedWaypoint[0].expire = nil + } + fetchedWaypoint[0].lastUpdated = Date() + do { + try context.save() + Logger.data.info("💾 Updated Node Waypoint App Packet For: \(fetchedWaypoint[0].id)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError)") + } + } } } catch { - Logger.data.error("Fetch Message To and From Users Error") + Logger.mesh.error("Error Deserializing WAYPOINT_APP packet.") } } } - -func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.waypoint.received %@".localized, String(packet.from)) - MeshLogger.log("📍 \(logString)") - - let fetchWaypointRequest = WaypointEntity.fetchRequest() - fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id)) - - do { - - if let waypointMessage = try? Waypoint(serializedData: packet.decoded.payload) { - let fetchedWaypoint = try context.fetch(fetchWaypointRequest) - if fetchedWaypoint.isEmpty { - let waypoint = WaypointEntity(context: context) - - waypoint.id = Int64(packet.id) - waypoint.name = waypointMessage.name - waypoint.longDescription = waypointMessage.description_p - waypoint.latitudeI = waypointMessage.latitudeI - waypoint.longitudeI = waypointMessage.longitudeI - waypoint.icon = Int64(waypointMessage.icon) - waypoint.locked = Int64(waypointMessage.lockedTo) - if waypointMessage.expire >= 1 { - waypoint.expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire))) - } else { - waypoint.expire = nil - } - waypoint.created = Date() - do { - try context.save() - Logger.data.info("💾 Added Node Waypoint App Packet For: \(waypoint.id)") - let manager = LocalNotificationManager() - let icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") - let latitude = Double(waypoint.latitudeI) / 1e7 - let longitude = Double(waypoint.longitudeI) / 1e7 - manager.notifications = [ - Notification( - id: ("notification.id.\(waypoint.id)"), - title: "New Waypoint Received", - subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", - content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", - target: "map", - path: "meshtastic://map?waypontid=\(waypoint.id)" - ) - ] - Logger.data.debug("meshtastic://map?waypontid=\(waypoint.id)") - manager.schedule() - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError)") - } - } else { - fetchedWaypoint[0].id = Int64(packet.id) - fetchedWaypoint[0].name = waypointMessage.name - fetchedWaypoint[0].longDescription = waypointMessage.description_p - fetchedWaypoint[0].latitudeI = waypointMessage.latitudeI - fetchedWaypoint[0].longitudeI = waypointMessage.longitudeI - fetchedWaypoint[0].icon = Int64(waypointMessage.icon) - fetchedWaypoint[0].locked = Int64(waypointMessage.lockedTo) - if waypointMessage.expire >= 1 { - fetchedWaypoint[0].expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire))) - } else { - fetchedWaypoint[0].expire = nil - } - fetchedWaypoint[0].lastUpdated = Date() - do { - try context.save() - Logger.data.info("💾 Updated Node Waypoint App Packet For: \(fetchedWaypoint[0].id)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError)") - } - } - } - } catch { - Logger.mesh.error("Error Deserializing WAYPOINT_APP packet.") - } -} diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 578f16d4..5bd3471a 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -11,9 +11,20 @@ import TipKit @main struct MeshtasticAppleApp: App { - @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate - let persistenceController = PersistenceController.shared - @ObservedObject private var bleManager: BLEManager = BLEManager.shared + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) + var appDelegate + + @ObservedObject + var bleManager: BLEManager + + @ObservedObject + var persistenceController: PersistenceController + + @ObservedObject + var updateCoreDataController: UpdateCoreDataController + + @ObservedObject + var queryCoreDataController: QueryCoreDataController @Environment(\.scenePhase) var scenePhase @@ -23,10 +34,29 @@ struct MeshtasticAppleApp: App { @State var addChannels = false @StateObject var appState = AppState.shared + init() { + let persistenceController = PersistenceController() + let backgroundContext = persistenceController.container.newBackgroundContext() + let updateCoreDataController = UpdateCoreDataController(context: backgroundContext) + let queryCoreDataController = QueryCoreDataController(context: persistenceController.container.viewContext) + + self.persistenceController = persistenceController + self.updateCoreDataController = updateCoreDataController + self.queryCoreDataController = queryCoreDataController + self.bleManager = BLEManager( + context: persistenceController.container.viewContext, + updateCoreDataController: updateCoreDataController, + queryCoreDataController: queryCoreDataController + ) + } + var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environmentObject(persistenceController) + .environmentObject(updateCoreDataController) + .environmentObject(queryCoreDataController) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager) @@ -41,7 +71,7 @@ struct MeshtasticAppleApp: App { if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false - if ((self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil) { + if (self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil { guard let cs = components.last!.components(separatedBy: "?").first else { return } diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 25f9a3e2..b8d3542f 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -8,9 +8,7 @@ import CoreData import OSLog -class PersistenceController { - - static let shared = PersistenceController() +class PersistenceController: ObservableObject { static var preview: PersistenceController = { let result = PersistenceController(inMemory: false) @@ -72,6 +70,29 @@ class PersistenceController { Logger.data.error("Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: \(error.localizedDescription)") } } + + public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes: Bool) { + for i in 0...container.managedObjectModel.entities.count-1 { + + let entity = container.managedObjectModel.entities[i] + let query = NSFetchRequest(entityName: entity.name!) + var deleteRequest = NSBatchDeleteRequest(fetchRequest: query) + let entityName = entity.name ?? "UNK" + + if includeRoutes { + deleteRequest = NSBatchDeleteRequest(fetchRequest: query) + } else if !includeRoutes { + if !(entityName.contains("RouteEntity") || entityName.contains("LocationEntity")) { + deleteRequest = NSBatchDeleteRequest(fetchRequest: query) + } + } + do { + try context.executeAndMergeChanges(using: deleteRequest) + } catch { + Logger.data.error("\(error.localizedDescription)") + } + } + } } extension NSManagedObjectContext { @@ -103,40 +124,7 @@ extension NSPersistentContainer { case invalidSource(String) } - /// Restore a persistent store for a URL `backupURL`. - /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. - /// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores. - /// - Throws: `CopyPersistentStoreError` in various situations. - /// - Returns: Nothing. If no errors are thrown, the restore is complete. -// func restorePersistentStore(from backupURL: URL) throws -> Void { -// guard backupURL.isFileURL else { -// throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL") -// } -// -// for persistentStoreDescription in persistentStoreDescriptions { -// guard let loadedStoreURL = persistentStoreDescription.url else { -// continue -// } -// guard FileManager.default.fileExists(atPath: backupURL.path) else { -// throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupURL)") -// } -// do { -// let storeOptions = persistentStoreDescription.options -// let configurationName = persistentStoreDescription.configuration -// let storeType = persistentStoreDescription.type -// -// // Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack. -// // When restoring, it's necessary to use the current persistent store coordinator. -// try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupURL, sourceOptions: storeOptions, ofType: storeType) -// // Add the persistent store at the same location we've been using, because it was removed in the previous step. -// try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions) -// } catch { -// throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)") -// } -// } -// } -// - /// Restore backup persistent stores located in the directory referenced by `backupURL`. + /// Restore backup persistent stores located in the directory referenced by `backupURL`. /// /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. /// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores. diff --git a/Meshtastic/Persistence/QueryCoreData.swift b/Meshtastic/Persistence/QueryCoreData.swift deleted file mode 100644 index f023eb9b..00000000 --- a/Meshtastic/Persistence/QueryCoreData.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// QueryCoreData.swift -// Meshtastic -// -// Created(c) Garth Vander Houwen 1/16/23. -// - -import CoreData - -public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoEntity? { - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(id)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - if fetchedNode.count == 1 { - return fetchedNode[0] - } - } catch { - return nil - } - return nil -} - -public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectContext) -> [UInt32] { - - let time = seconds * -1 - let fetchMessagesRequest = MessageEntity.fetchRequest() - let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date()) - let milleseconds = Int32(timeRange?.timeIntervalSince1970 ?? 0) - fetchMessagesRequest.predicate = NSPredicate(format: "receivedTimestamp >= %d", milleseconds) - - do { - let fetchedMessages = try context.fetch(fetchMessagesRequest) - if fetchedMessages.count == 1 { - return fetchedMessages.map { UInt32($0.messageId) } - } - } catch { - return [] - } - return [] -} - -public func getTraceRoute(id: Int64, context: NSManagedObjectContext) -> TraceRouteEntity? { - - let fetchTraceRouteRequest = TraceRouteEntity.fetchRequest() - fetchTraceRouteRequest.predicate = NSPredicate(format: "id == %lld", Int64(id)) - - do { - let fetchedTraceRoute = try context.fetch(fetchTraceRouteRequest) - if fetchedTraceRoute.count == 1 { - return fetchedTraceRoute[0] - } - } catch { - return nil - } - return nil -} - -public func getUser(id: Int64, context: NSManagedObjectContext) -> UserEntity { - - let fetchUserRequest = UserEntity.fetchRequest() - fetchUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(id)) - - do { - let fetchedUser = try context.fetch(fetchUserRequest) - if fetchedUser.count == 1 { - return fetchedUser[0] - } - } catch { - return UserEntity(context: context) - } - return UserEntity(context: context) -} - -public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointEntity { - - let fetchWaypointRequest = WaypointEntity.fetchRequest() - fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(id)) - - do { - let fetchedWaypoint = try context.fetch(fetchWaypointRequest) - if fetchedWaypoint.count == 1 { - return fetchedWaypoint[0] - } - } catch { - return WaypointEntity(context: context) - } - return WaypointEntity(context: context) -} diff --git a/Meshtastic/Persistence/QueryCoreDataController.swift b/Meshtastic/Persistence/QueryCoreDataController.swift new file mode 100644 index 00000000..f12cb53d --- /dev/null +++ b/Meshtastic/Persistence/QueryCoreDataController.swift @@ -0,0 +1,99 @@ +// +// QueryCoreData.swift +// Meshtastic +// +// Created(c) Garth Vander Houwen 1/16/23. +// + +import CoreData + +class QueryCoreDataController: ObservableObject { + private let context: NSManagedObjectContext + + init(context: NSManagedObjectContext) { + self.context = context + } + + public func getNodeInfo(id: Int64) -> NodeInfoEntity? { + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(id)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + if fetchedNode.count == 1 { + return fetchedNode[0] + } + } catch { + return nil + } + return nil + } + + public func getStoreAndForwardMessageIds(seconds: Int) -> [UInt32] { + + let time = seconds * -1 + let fetchMessagesRequest = MessageEntity.fetchRequest() + let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date()) + let milleseconds = Int32(timeRange?.timeIntervalSince1970 ?? 0) + fetchMessagesRequest.predicate = NSPredicate(format: "receivedTimestamp >= %d", milleseconds) + + do { + let fetchedMessages = try context.fetch(fetchMessagesRequest) + if fetchedMessages.count == 1 { + return fetchedMessages.map { UInt32($0.messageId) } + } + } catch { + return [] + } + return [] + } + + public func getTraceRoute(id: Int64) -> TraceRouteEntity? { + + let fetchTraceRouteRequest = TraceRouteEntity.fetchRequest() + fetchTraceRouteRequest.predicate = NSPredicate(format: "id == %lld", Int64(id)) + + do { + let fetchedTraceRoute = try context.fetch(fetchTraceRouteRequest) + if fetchedTraceRoute.count == 1 { + return fetchedTraceRoute[0] + } + } catch { + return nil + } + return nil + } + + public func getUser(id: Int64) -> UserEntity { + + let fetchUserRequest = UserEntity.fetchRequest() + fetchUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(id)) + + do { + let fetchedUser = try context.fetch(fetchUserRequest) + if fetchedUser.count == 1 { + return fetchedUser[0] + } + } catch { + return UserEntity(context: context) + } + return UserEntity(context: context) + } + + public func getWaypoint(id: Int64) -> WaypointEntity { + + let fetchWaypointRequest = WaypointEntity.fetchRequest() + fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(id)) + + do { + let fetchedWaypoint = try context.fetch(fetchWaypointRequest) + if fetchedWaypoint.count == 1 { + return fetchedWaypoint[0] + } + } catch { + return WaypointEntity(context: context) + } + return WaypointEntity(context: context) + } +} diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift deleted file mode 100644 index cbf63ce5..00000000 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ /dev/null @@ -1,1328 +0,0 @@ -// -// UpdateCoreData.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 10/3/22. - -import CoreData -import MeshtasticProtobufs -import OSLog - -public func clearPax(destNum: Int64, context: NSManagedObjectContext) -> Bool { - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - let newPax = [PaxCounterLog]() - fetchedNode[0].pax? = NSOrderedSet(array: newPax) - do { - try context.save() - return true - - } catch { - context.rollback() - return false - } - } catch { - Logger.data.error("💥 [NodeInfoEntity] fetch data error") - return false - } -} - -public func clearPositions(destNum: Int64, context: NSManagedObjectContext) -> Bool { - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - let newPostions = [PositionEntity]() - fetchedNode[0].positions? = NSOrderedSet(array: newPostions) - do { - try context.save() - return true - - } catch { - context.rollback() - return false - } - } catch { - Logger.data.error("💥 [NodeInfoEntity] fetch data error") - return false - } -} - -public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManagedObjectContext) -> Bool { - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - let emptyTelemetry = [TelemetryEntity]() - fetchedNode[0].telemetries? = NSOrderedSet(array: emptyTelemetry) - do { - try context.save() - return true - - } catch { - context.rollback() - return false - } - } catch { - Logger.data.error("💥 [NodeInfoEntity] fetch data error") - return false - } -} - -public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObjectContext) { - do { - let objects = channel.allPrivateMessages - for object in objects { - context.delete(object) - } - try context.save() - } catch let error as NSError { - Logger.data.error("\(error.localizedDescription)") - } -} - -public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext) { - - do { - let objects = user.messageList - for object in objects { - context.delete(object) - } - try context.save() - } catch let error as NSError { - Logger.data.error("\(error.localizedDescription)") - } -} - -public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes: Bool) { - - let persistenceController = PersistenceController.shared.container - for i in 0...persistenceController.managedObjectModel.entities.count-1 { - - let entity = persistenceController.managedObjectModel.entities[i] - let query = NSFetchRequest(entityName: entity.name!) - var deleteRequest = NSBatchDeleteRequest(fetchRequest: query) - let entityName = entity.name ?? "UNK" - - if includeRoutes { - deleteRequest = NSBatchDeleteRequest(fetchRequest: query) - } else if !includeRoutes { - if !(entityName.contains("RouteEntity") || entityName.contains("LocationEntity")) { - deleteRequest = NSBatchDeleteRequest(fetchRequest: query) - } - } - do { - try context.executeAndMergeChanges(using: deleteRequest) - } catch { - Logger.data.error("\(error.localizedDescription)") - } - } -} - -func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, packet.from.toHex()) - MeshLogger.log("📟 \(logString)") - - guard packet.from > 0 else { return } - - let fetchNodeInfoAppRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - - let fetchedNode = try context.fetch(fetchNodeInfoAppRequest) - if fetchedNode.count == 0 { - // Not Found Insert - let newNode = NodeInfoEntity(context: context) - newNode.id = Int64(packet.from) - newNode.num = Int64(packet.from) - if packet.rxTime > 0 { - newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - } - newNode.snr = packet.rxSnr - newNode.rssi = packet.rxRssi - newNode.viaMqtt = packet.viaMqtt - - if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { - newNode.channel = Int32(packet.channel) - } - if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { - newNode.hopsAway = Int32(nodeInfoMessage.hopsAway) - newNode.favorite = nodeInfoMessage.isFavorite - } - - if let newUserMessage = try? User(serializedData: packet.decoded.payload) { - - if newUserMessage.id.isEmpty { - if packet.from > Int16.max { - let newUser = createUser(num: Int64(packet.from), context: context) - newNode.user = newUser - } - } else { - - let newUser = UserEntity(context: context) - newUser.userId = newUserMessage.id - newUser.num = Int64(packet.from) - newUser.longName = newUserMessage.longName - newUser.shortName = newUserMessage.shortName - newUser.role = Int32(newUserMessage.role.rawValue) - newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() - newNode.user = newUser - - if UserDefaults.newNodeNotifications { - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: (UUID().uuidString), - title: "New Node", - subtitle: "\(newUser.longName ?? "unknown".localized)", - content: "New Node has been discovered", - target: "nodes", - path: "meshtastic://nodes?nodenum=\(newUser.num)" - ) - ] - manager.schedule() - } - } - } else { - if packet.from > Int16.max { - let newUser = createUser(num: Int64(packet.from), context: context) - newNode.user = newUser - } - } - - if newNode.user == nil && packet.from > Int16.max { - newNode.user = createUser(num: Int64(packet.from), context: context) - } - - let myInfoEntity = MyInfoEntity(context: context) - myInfoEntity.myNodeNum = Int64(packet.from) - myInfoEntity.rebootCount = 0 - do { - try context.save() - Logger.data.info("💾 [MyInfoEntity] Saved a new myInfo for node number: \(packet.from.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [MyInfoEntity] Error Inserting New Core Data: \(nsError, privacy: .public)") - } - newNode.myInfo = myInfoEntity - - } else { - // Update an existing node - fetchedNode[0].id = Int64(packet.from) - fetchedNode[0].num = Int64(packet.from) - if packet.rxTime > 0 { - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - if fetchedNode[0].firstHeard == nil { - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - } - } - fetchedNode[0].snr = packet.rxSnr - fetchedNode[0].rssi = packet.rxRssi - fetchedNode[0].viaMqtt = packet.viaMqtt - if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { - fetchedNode[0].channel = Int32(packet.channel) - } - - if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { - - fetchedNode[0].hopsAway = Int32(nodeInfoMessage.hopsAway) - fetchedNode[0].favorite = nodeInfoMessage.isFavorite - if nodeInfoMessage.hasDeviceMetrics { - let telemetry = TelemetryEntity(context: context) - telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel) - telemetry.voltage = nodeInfoMessage.deviceMetrics.voltage - telemetry.channelUtilization = nodeInfoMessage.deviceMetrics.channelUtilization - telemetry.airUtilTx = nodeInfoMessage.deviceMetrics.airUtilTx - var newTelemetries = [TelemetryEntity]() - newTelemetries.append(telemetry) - fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries) - } - if nodeInfoMessage.hasUser { - /// Seeing Some crashes here ? - fetchedNode[0].user!.userId = nodeInfoMessage.user.id - fetchedNode[0].user!.num = Int64(nodeInfoMessage.num) - fetchedNode[0].user!.longName = nodeInfoMessage.user.longName - fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName - fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) - fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() - } - } else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart { - fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) - } - if fetchedNode[0].user == nil { - let newUser = createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) - fetchedNode[0].user! = newUser - - } - do { - try context.save() - Logger.data.info("💾 [NodeInfoEntity] Updated from Node Info App Packet For: \(fetchedNode[0].num.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [NodeInfoEntity] Error Saving from NODEINFO_APP \(nsError, privacy: .public)") - } - } - } catch { - Logger.data.error("💥 [NodeInfoEntity] fetch data error for NODEINFO_APP") - } -} - -func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.position.received %@".localized, String(packet.from)) - MeshLogger.log("📍 \(logString)") - - let fetchNodePositionRequest = NodeInfoEntity.fetchRequest() - fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - - if let positionMessage = try? Position(serializedData: packet.decoded.payload) { - - /// Don't save empty position packets from null island or apple park - if (positionMessage.longitudeI != 0 && positionMessage.latitudeI != 0) && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) { - let fetchedNode = try context.fetch(fetchNodePositionRequest) - if fetchedNode.count == 1 { - - // Unset the current latest position for this node - let fetchCurrentLatestPositionsRequest = PositionEntity.fetchRequest() - fetchCurrentLatestPositionsRequest.predicate = NSPredicate(format: "nodePosition.num == %lld && latest = true", Int64(packet.from)) - - let fetchedPositions = try context.fetch(fetchCurrentLatestPositionsRequest) - if fetchedPositions.count > 0 { - for position in fetchedPositions { - position.latest = false - } - } - let position = PositionEntity(context: context) - position.latest = true - position.snr = packet.rxSnr - position.rssi = packet.rxRssi - position.seqNo = Int32(positionMessage.seqNumber) - position.latitudeI = positionMessage.latitudeI - position.longitudeI = positionMessage.longitudeI - position.altitude = positionMessage.altitude - position.satsInView = Int32(positionMessage.satsInView) - position.speed = Int32(positionMessage.groundSpeed) - let heading = Int32(positionMessage.groundTrack) - // Throw out bad haeadings from the device - if heading >= 0 && heading <= 360 { - position.heading = Int32(positionMessage.groundTrack) - } - position.precisionBits = Int32(positionMessage.precisionBits) - if positionMessage.timestamp != 0 { - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp))) - } else { - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) - } - guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { - return - } - /// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one. - if mutablePositions.count > 0 && (position.precisionBits == 32 || position.precisionBits == 0) { - if let mostRecent = mutablePositions.lastObject as? PositionEntity, mostRecent.coordinate.distance(from: position.coordinate) < 15.0 { - mutablePositions.remove(mostRecent) - } - } else if mutablePositions.count > 0 { - /// Don't store any history for reduced accuracy positions, we will just show a circle - mutablePositions.removeAllObjects() - } - mutablePositions.add(position) - fetchedNode[0].id = Int64(packet.from) - fetchedNode[0].num = Int64(packet.from) - if positionMessage.time > 0 { - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) - } else if packet.rxTime > 0 { - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - } - fetchedNode[0].snr = packet.rxSnr - fetchedNode[0].rssi = packet.rxRssi - fetchedNode[0].viaMqtt = packet.viaMqtt - fetchedNode[0].channel = Int32(packet.channel) - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet - - do { - try context.save() - Logger.data.info("💾 [Position] Saved from Position App Packet For: \(fetchedNode[0].num.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError, privacy: .public)") - } - } - } else { - - if (try? NodeInfo(serializedData: packet.decoded.payload)) != nil { - upsertNodeInfoPacket(packet: packet, context: context) - } else { - Logger.data.error("💥 Empty POSITION_APP Packet: \((try? packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") - } - } - } - } catch { - Logger.data.error("💥 Error Deserializing POSITION_APP packet.") - } -} - -func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum)) - MeshLogger.log("📶 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Device Config - if !fetchedNode.isEmpty { - if fetchedNode[0].bluetoothConfig == nil { - let newBluetoothConfig = BluetoothConfigEntity(context: context) - newBluetoothConfig.enabled = config.enabled - newBluetoothConfig.mode = Int32(config.mode.rawValue) - newBluetoothConfig.fixedPin = Int32(config.fixedPin) - newBluetoothConfig.deviceLoggingEnabled = config.deviceLoggingEnabled - fetchedNode[0].bluetoothConfig = newBluetoothConfig - } else { - fetchedNode[0].bluetoothConfig?.enabled = config.enabled - fetchedNode[0].bluetoothConfig?.mode = Int32(config.mode.rawValue) - fetchedNode[0].bluetoothConfig?.fixedPin = Int32(config.fixedPin) - fetchedNode[0].bluetoothConfig?.deviceLoggingEnabled = config.deviceLoggingEnabled - } - do { - try context.save() - Logger.data.info("💾 [BluetoothConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [BluetoothConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [BluetoothConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Bluetooth Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [BluetoothConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum)) - MeshLogger.log("📟 \(logString)") - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Device Config - if !fetchedNode.isEmpty { - if fetchedNode[0].deviceConfig == nil { - let newDeviceConfig = DeviceConfigEntity(context: context) - newDeviceConfig.role = Int32(config.role.rawValue) - newDeviceConfig.serialEnabled = config.serialEnabled - newDeviceConfig.debugLogEnabled = config.debugLogEnabled - newDeviceConfig.buttonGpio = Int32(config.buttonGpio) - newDeviceConfig.buzzerGpio = Int32(config.buzzerGpio) - newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue) - newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) - newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress - newDeviceConfig.ledHeartbeatEnabled = !config.ledHeartbeatDisabled - newDeviceConfig.isManaged = config.isManaged - newDeviceConfig.tzdef = config.tzdef - fetchedNode[0].deviceConfig = newDeviceConfig - } else { - fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue) - fetchedNode[0].deviceConfig?.serialEnabled = config.serialEnabled - fetchedNode[0].deviceConfig?.debugLogEnabled = config.debugLogEnabled - fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.buttonGpio) - fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.buzzerGpio) - fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue) - fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) - fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress - fetchedNode[0].deviceConfig?.ledHeartbeatEnabled = !config.ledHeartbeatDisabled - fetchedNode[0].deviceConfig?.isManaged = config.isManaged - fetchedNode[0].deviceConfig?.tzdef = config.tzdef - } - do { - try context.save() - Logger.data.info("💾 [DeviceConfigEntity] Updated Device Config for node number: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [DeviceConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [DeviceConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex()) - MeshLogger.log("🖥️ \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Device Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].displayConfig == nil { - - let newDisplayConfig = DisplayConfigEntity(context: context) - newDisplayConfig.gpsFormat = Int32(config.gpsFormat.rawValue) - newDisplayConfig.screenOnSeconds = Int32(truncatingIfNeeded: config.screenOnSecs) - newDisplayConfig.screenCarouselInterval = Int32(truncatingIfNeeded: config.autoScreenCarouselSecs) - newDisplayConfig.compassNorthTop = config.compassNorthTop - newDisplayConfig.flipScreen = config.flipScreen - newDisplayConfig.oledType = Int32(config.oled.rawValue) - newDisplayConfig.displayMode = Int32(config.displaymode.rawValue) - newDisplayConfig.units = Int32(config.units.rawValue) - newDisplayConfig.headingBold = config.headingBold - fetchedNode[0].displayConfig = newDisplayConfig - - } else { - - fetchedNode[0].displayConfig?.gpsFormat = Int32(config.gpsFormat.rawValue) - fetchedNode[0].displayConfig?.screenOnSeconds = Int32(truncatingIfNeeded: config.screenOnSecs) - fetchedNode[0].displayConfig?.screenCarouselInterval = Int32(truncatingIfNeeded: config.autoScreenCarouselSecs) - fetchedNode[0].displayConfig?.compassNorthTop = config.compassNorthTop - fetchedNode[0].displayConfig?.flipScreen = config.flipScreen - fetchedNode[0].displayConfig?.oledType = Int32(config.oled.rawValue) - fetchedNode[0].displayConfig?.displayMode = Int32(config.displaymode.rawValue) - fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue) - fetchedNode[0].displayConfig?.headingBold = config.headingBold - } - - do { - - try context.save() - Logger.data.info("💾 [DisplayConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - - } catch { - - context.rollback() - - let nsError = error as NSError - Logger.data.error("💥 [DisplayConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - - Logger.data.error("💥 [DisplayConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex()) unable to save Display Config") - } - - } catch { - - let nsError = error as NSError - Logger.data.error("💥 [DisplayConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex()) - MeshLogger.log("📻 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", nodeNum) - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save LoRa Config - if fetchedNode.count > 0 { - if fetchedNode[0].loRaConfig == nil { - // No lora config for node, save a new lora config - let newLoRaConfig = LoRaConfigEntity(context: context) - newLoRaConfig.regionCode = Int32(config.region.rawValue) - newLoRaConfig.usePreset = config.usePreset - newLoRaConfig.modemPreset = Int32(config.modemPreset.rawValue) - newLoRaConfig.bandwidth = Int32(config.bandwidth) - newLoRaConfig.spreadFactor = Int32(config.spreadFactor) - newLoRaConfig.codingRate = Int32(config.codingRate) - newLoRaConfig.frequencyOffset = config.frequencyOffset - newLoRaConfig.overrideFrequency = config.overrideFrequency - newLoRaConfig.overrideDutyCycle = config.overrideDutyCycle - newLoRaConfig.hopLimit = Int32(config.hopLimit) - newLoRaConfig.txPower = Int32(config.txPower) - newLoRaConfig.txEnabled = config.txEnabled - newLoRaConfig.channelNum = Int32(config.channelNum) - newLoRaConfig.sx126xRxBoostedGain = config.sx126XRxBoostedGain - newLoRaConfig.ignoreMqtt = config.ignoreMqtt - fetchedNode[0].loRaConfig = newLoRaConfig - } else { - fetchedNode[0].loRaConfig?.regionCode = Int32(config.region.rawValue) - fetchedNode[0].loRaConfig?.usePreset = config.usePreset - fetchedNode[0].loRaConfig?.modemPreset = Int32(config.modemPreset.rawValue) - fetchedNode[0].loRaConfig?.bandwidth = Int32(config.bandwidth) - fetchedNode[0].loRaConfig?.spreadFactor = Int32(config.spreadFactor) - fetchedNode[0].loRaConfig?.codingRate = Int32(config.codingRate) - fetchedNode[0].loRaConfig?.frequencyOffset = config.frequencyOffset - fetchedNode[0].loRaConfig?.overrideFrequency = config.overrideFrequency - fetchedNode[0].loRaConfig?.overrideDutyCycle = config.overrideDutyCycle - fetchedNode[0].loRaConfig?.hopLimit = Int32(config.hopLimit) - fetchedNode[0].loRaConfig?.txPower = Int32(config.txPower) - fetchedNode[0].loRaConfig?.txEnabled = config.txEnabled - fetchedNode[0].loRaConfig?.channelNum = Int32(config.channelNum) - fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain - fetchedNode[0].loRaConfig?.ignoreMqtt = config.ignoreMqtt - fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain - } - do { - try context.save() - Logger.data.info("💾 [LoRaConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [LoRaConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [LoRaConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Lora Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [LoRaConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum)) - MeshLogger.log("🌐 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save WiFi Config - if !fetchedNode.isEmpty { - if fetchedNode[0].networkConfig == nil { - let newNetworkConfig = NetworkConfigEntity(context: context) - newNetworkConfig.wifiEnabled = config.wifiEnabled - newNetworkConfig.wifiSsid = config.wifiSsid - newNetworkConfig.wifiPsk = config.wifiPsk - newNetworkConfig.ethEnabled = config.ethEnabled - fetchedNode[0].networkConfig = newNetworkConfig - } else { - fetchedNode[0].networkConfig?.ethEnabled = config.ethEnabled - fetchedNode[0].networkConfig?.wifiEnabled = config.wifiEnabled - fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid - fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk - } - - do { - try context.save() - Logger.data.info("💾 [NetworkConfigEntity] Updated Network Config for node: \(nodeNum.toHex(), privacy: .public)") - - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [NetworkConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [NetworkConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Network Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [NetworkConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum)) - MeshLogger.log("🗺️ \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save LoRa Config - if !fetchedNode.isEmpty { - if fetchedNode[0].positionConfig == nil { - let newPositionConfig = PositionConfigEntity(context: context) - newPositionConfig.smartPositionEnabled = config.positionBroadcastSmartEnabled - newPositionConfig.deviceGpsEnabled = config.gpsEnabled - newPositionConfig.gpsMode = Int32(config.gpsMode.rawValue) - newPositionConfig.rxGpio = Int32(config.rxGpio) - newPositionConfig.txGpio = Int32(config.txGpio) - newPositionConfig.gpsEnGpio = Int32(config.gpsEnGpio) - newPositionConfig.fixedPosition = config.fixedPosition - newPositionConfig.positionBroadcastSeconds = Int32(truncatingIfNeeded: config.positionBroadcastSecs) - newPositionConfig.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs) - newPositionConfig.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance) - newPositionConfig.positionFlags = Int32(config.positionFlags) - newPositionConfig.gpsAttemptTime = 900 - newPositionConfig.gpsUpdateInterval = Int32(config.gpsUpdateInterval) - fetchedNode[0].positionConfig = newPositionConfig - } else { - fetchedNode[0].positionConfig?.smartPositionEnabled = config.positionBroadcastSmartEnabled - fetchedNode[0].positionConfig?.deviceGpsEnabled = config.gpsEnabled - fetchedNode[0].positionConfig?.gpsMode = Int32(config.gpsMode.rawValue) - fetchedNode[0].positionConfig?.rxGpio = Int32(config.rxGpio) - fetchedNode[0].positionConfig?.txGpio = Int32(config.txGpio) - fetchedNode[0].positionConfig?.gpsEnGpio = Int32(config.gpsEnGpio) - fetchedNode[0].positionConfig?.fixedPosition = config.fixedPosition - fetchedNode[0].positionConfig?.positionBroadcastSeconds = Int32(truncatingIfNeeded: config.positionBroadcastSecs) - fetchedNode[0].positionConfig?.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs) - fetchedNode[0].positionConfig?.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance) - fetchedNode[0].positionConfig?.gpsAttemptTime = 900 - fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.gpsUpdateInterval) - fetchedNode[0].positionConfig?.positionFlags = Int32(config.positionFlags) - } - do { - try context.save() - Logger.data.info("💾 [PositionConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [PositionConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [PositionConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Position Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [PositionConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context: NSManagedObjectContext) { - let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum)) - MeshLogger.log("🗺️ \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Power Config - if !fetchedNode.isEmpty { - if fetchedNode[0].powerConfig == nil { - let newPowerConfig = PowerConfigEntity(context: context) - newPowerConfig.adcMultiplierOverride = config.adcMultiplierOverride - newPowerConfig.deviceBatteryInaAddress = Int32(config.deviceBatteryInaAddress) - newPowerConfig.isPowerSaving = config.isPowerSaving - newPowerConfig.lsSecs = Int32(truncatingIfNeeded: config.lsSecs) - newPowerConfig.minWakeSecs = Int32(truncatingIfNeeded: config.minWakeSecs) - newPowerConfig.onBatteryShutdownAfterSecs = Int32(truncatingIfNeeded: config.onBatteryShutdownAfterSecs) - newPowerConfig.waitBluetoothSecs = Int32(truncatingIfNeeded: config.waitBluetoothSecs) - fetchedNode[0].powerConfig = newPowerConfig - } else { - fetchedNode[0].powerConfig?.adcMultiplierOverride = config.adcMultiplierOverride - fetchedNode[0].powerConfig?.deviceBatteryInaAddress = Int32(config.deviceBatteryInaAddress) - fetchedNode[0].powerConfig?.isPowerSaving = config.isPowerSaving - fetchedNode[0].powerConfig?.lsSecs = Int32(truncatingIfNeeded: config.lsSecs) - fetchedNode[0].powerConfig?.minWakeSecs = Int32(truncatingIfNeeded: config.minWakeSecs) - fetchedNode[0].powerConfig?.onBatteryShutdownAfterSecs = Int32(truncatingIfNeeded: config.onBatteryShutdownAfterSecs) - fetchedNode[0].powerConfig?.waitBluetoothSecs = Int32(truncatingIfNeeded: config.waitBluetoothSecs) - } - do { - try context.save() - Logger.data.info("💾 [PowerConfigEntity] Updated Power Config for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [PowerConfigEntity] Error Updating Core Data PowerConfigEntity: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [PowerConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Power Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [PowerConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum)) - MeshLogger.log("🏮 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Ambient Lighting Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].cannedMessageConfig == nil { - - let newAmbientLightingConfig = AmbientLightingConfigEntity(context: context) - - newAmbientLightingConfig.ledState = config.ledState - newAmbientLightingConfig.current = Int32(config.current) - newAmbientLightingConfig.red = Int32(config.red) - newAmbientLightingConfig.green = Int32(config.green) - newAmbientLightingConfig.blue = Int32(config.blue) - fetchedNode[0].ambientLightingConfig = newAmbientLightingConfig - - } else { - - if fetchedNode[0].ambientLightingConfig == nil { - fetchedNode[0].ambientLightingConfig = AmbientLightingConfigEntity(context: context) - } - fetchedNode[0].ambientLightingConfig?.ledState = config.ledState - fetchedNode[0].ambientLightingConfig?.current = Int32(config.current) - fetchedNode[0].ambientLightingConfig?.red = Int32(config.red) - fetchedNode[0].ambientLightingConfig?.green = Int32(config.green) - fetchedNode[0].ambientLightingConfig?.blue = Int32(config.blue) - } - - do { - try context.save() - Logger.data.info("💾 [AmbientLightingConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [AmbientLightingConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [AmbientLightingConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Ambient Lighting Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [AmbientLightingConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum)) - MeshLogger.log("🥫 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Canned Message Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].cannedMessageConfig == nil { - - let newCannedMessageConfig = CannedMessageConfigEntity(context: context) - - newCannedMessageConfig.enabled = config.enabled - newCannedMessageConfig.sendBell = config.sendBell - newCannedMessageConfig.rotary1Enabled = config.rotary1Enabled - newCannedMessageConfig.updown1Enabled = config.updown1Enabled - newCannedMessageConfig.inputbrokerPinA = Int32(config.inputbrokerPinA) - newCannedMessageConfig.inputbrokerPinB = Int32(config.inputbrokerPinB) - newCannedMessageConfig.inputbrokerPinPress = Int32(config.inputbrokerPinPress) - newCannedMessageConfig.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue) - newCannedMessageConfig.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue) - newCannedMessageConfig.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue) - - fetchedNode[0].cannedMessageConfig = newCannedMessageConfig - - } else { - - fetchedNode[0].cannedMessageConfig?.enabled = config.enabled - fetchedNode[0].cannedMessageConfig?.sendBell = config.sendBell - fetchedNode[0].cannedMessageConfig?.rotary1Enabled = config.rotary1Enabled - fetchedNode[0].cannedMessageConfig?.updown1Enabled = config.updown1Enabled - fetchedNode[0].cannedMessageConfig?.inputbrokerPinA = Int32(config.inputbrokerPinA) - fetchedNode[0].cannedMessageConfig?.inputbrokerPinB = Int32(config.inputbrokerPinB) - fetchedNode[0].cannedMessageConfig?.inputbrokerPinPress = Int32(config.inputbrokerPinPress) - fetchedNode[0].cannedMessageConfig?.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue) - fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue) - fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue) - } - - do { - try context.save() - Logger.data.info("💾 [CannedMessageConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [CannedMessageConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [CannedMessageConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Canned Message Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [CannedMessageConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) - MeshLogger.log("🕵️ \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Detection Sensor Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].detectionSensorConfig == nil { - - let newConfig = DetectionSensorConfigEntity(context: context) - newConfig.enabled = config.enabled - newConfig.sendBell = config.sendBell - newConfig.name = config.name - - newConfig.monitorPin = Int32(config.monitorPin) - newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh - newConfig.usePullup = config.usePullup - newConfig.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) - newConfig.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) - fetchedNode[0].detectionSensorConfig = newConfig - - } else { - fetchedNode[0].detectionSensorConfig?.enabled = config.enabled - fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell - fetchedNode[0].detectionSensorConfig?.name = config.name - fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) - fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup - fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh - fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) - fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) - } - - do { - try context.save() - Logger.data.info("💾 [DetectionSensorConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [DetectionSensorConfigEntity] Error Updating Core Data : \(nsError, privacy: .public)") - } - - } else { - Logger.data.error("💥 [DetectionSensorConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Detection Sensor Module Config") - } - - } catch { - let nsError = error as NSError - Logger.data.error("💥 [DetectionSensorConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum)) - MeshLogger.log("📣 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save External Notificaitone Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].externalNotificationConfig == nil { - let newExternalNotificationConfig = ExternalNotificationConfigEntity(context: context) - newExternalNotificationConfig.enabled = config.enabled - newExternalNotificationConfig.usePWM = config.usePwm - newExternalNotificationConfig.alertBell = config.alertBell - newExternalNotificationConfig.alertBellBuzzer = config.alertBellBuzzer - newExternalNotificationConfig.alertBellVibra = config.alertBellVibra - newExternalNotificationConfig.alertMessage = config.alertMessage - newExternalNotificationConfig.alertMessageBuzzer = config.alertMessageBuzzer - newExternalNotificationConfig.alertMessageVibra = config.alertMessageVibra - newExternalNotificationConfig.active = config.active - newExternalNotificationConfig.output = Int32(config.output) - newExternalNotificationConfig.outputBuzzer = Int32(config.outputBuzzer) - newExternalNotificationConfig.outputVibra = Int32(config.outputVibra) - newExternalNotificationConfig.outputMilliseconds = Int32(config.outputMs) - newExternalNotificationConfig.nagTimeout = Int32(config.nagTimeout) - newExternalNotificationConfig.useI2SAsBuzzer = config.useI2SAsBuzzer - fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig - - } else { - fetchedNode[0].externalNotificationConfig?.enabled = config.enabled - fetchedNode[0].externalNotificationConfig?.usePWM = config.usePwm - fetchedNode[0].externalNotificationConfig?.alertBell = config.alertBell - fetchedNode[0].externalNotificationConfig?.alertBellBuzzer = config.alertBellBuzzer - fetchedNode[0].externalNotificationConfig?.alertBellVibra = config.alertBellVibra - fetchedNode[0].externalNotificationConfig?.alertMessage = config.alertMessage - fetchedNode[0].externalNotificationConfig?.alertMessageBuzzer = config.alertMessageBuzzer - fetchedNode[0].externalNotificationConfig?.alertMessageVibra = config.alertMessageVibra - fetchedNode[0].externalNotificationConfig?.active = config.active - fetchedNode[0].externalNotificationConfig?.output = Int32(config.output) - fetchedNode[0].externalNotificationConfig?.outputBuzzer = Int32(config.outputBuzzer) - fetchedNode[0].externalNotificationConfig?.outputVibra = Int32(config.outputVibra) - fetchedNode[0].externalNotificationConfig?.outputMilliseconds = Int32(config.outputMs) - fetchedNode[0].externalNotificationConfig?.nagTimeout = Int32(config.nagTimeout) - fetchedNode[0].externalNotificationConfig?.useI2SAsBuzzer = config.useI2SAsBuzzer - } - - do { - try context.save() - Logger.data.info("💾 [ExternalNotificationConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [ExternalNotificationConfigEntity] Error Updating Core Data : \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [ExternalNotificationConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save External Notification Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [ExternalNotificationConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.paxcounter.config %@".localized, String(nodeNum)) - MeshLogger.log("🧑‍🤝‍🧑 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save PAX Counter Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].paxCounterConfig == nil { - let newPaxCounterConfig = PaxCounterConfigEntity(context: context) - newPaxCounterConfig.enabled = config.enabled - newPaxCounterConfig.updateInterval = Int32(config.paxcounterUpdateInterval) - - fetchedNode[0].paxCounterConfig = newPaxCounterConfig - - } else { - fetchedNode[0].paxCounterConfig?.enabled = config.enabled - fetchedNode[0].paxCounterConfig?.updateInterval = Int32(config.paxcounterUpdateInterval) - } - - do { - try context.save() - Logger.data.info("💾 [PaxCounterConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [PaxCounterConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [PaxCounterConfigEntity] No Nodes found in local database matching node number \(nodeNum.toHex(), privacy: .public) unable to save PAX Counter Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [PaxCounterConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum)) - MeshLogger.log("⛰️ \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save RTTTL Config - if !fetchedNode.isEmpty { - if fetchedNode[0].rtttlConfig == nil { - let newRtttlConfig = RTTTLConfigEntity(context: context) - newRtttlConfig.ringtone = ringtone - fetchedNode[0].rtttlConfig = newRtttlConfig - } else { - fetchedNode[0].rtttlConfig?.ringtone = ringtone - } - do { - try context.save() - Logger.data.info("💾 [RtttlConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [RtttlConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [RtttlConfigEntity] No nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save RTTTL Ringtone Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [RtttlConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum)) - MeshLogger.log("🌉 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save MQTT Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].mqttConfig == nil { - let newMQTTConfig = MQTTConfigEntity(context: context) - newMQTTConfig.enabled = config.enabled - newMQTTConfig.proxyToClientEnabled = config.proxyToClientEnabled - newMQTTConfig.address = config.address - newMQTTConfig.username = config.username - newMQTTConfig.password = config.password - newMQTTConfig.root = config.root - newMQTTConfig.encryptionEnabled = config.encryptionEnabled - newMQTTConfig.jsonEnabled = config.jsonEnabled - newMQTTConfig.tlsEnabled = config.tlsEnabled - newMQTTConfig.mapReportingEnabled = config.mapReportingEnabled - newMQTTConfig.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) - newMQTTConfig.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) - fetchedNode[0].mqttConfig = newMQTTConfig - } else { - fetchedNode[0].mqttConfig?.enabled = config.enabled - fetchedNode[0].mqttConfig?.proxyToClientEnabled = config.proxyToClientEnabled - fetchedNode[0].mqttConfig?.address = config.address - fetchedNode[0].mqttConfig?.username = config.username - fetchedNode[0].mqttConfig?.password = config.password - fetchedNode[0].mqttConfig?.root = config.root - fetchedNode[0].mqttConfig?.encryptionEnabled = config.encryptionEnabled - fetchedNode[0].mqttConfig?.jsonEnabled = config.jsonEnabled - fetchedNode[0].mqttConfig?.tlsEnabled = config.tlsEnabled - fetchedNode[0].mqttConfig?.mapReportingEnabled = config.mapReportingEnabled - fetchedNode[0].mqttConfig?.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) - fetchedNode[0].mqttConfig?.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) - } - do { - try context.save() - Logger.data.info("💾 [MQTTConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [MQTTConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [MQTTConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save MQTT Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [MQTTConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum)) - MeshLogger.log("⛰️ \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Device Config - if !fetchedNode.isEmpty { - if fetchedNode[0].rangeTestConfig == nil { - let newRangeTestConfig = RangeTestConfigEntity(context: context) - newRangeTestConfig.sender = Int32(config.sender) - newRangeTestConfig.enabled = config.enabled - newRangeTestConfig.save = config.save - fetchedNode[0].rangeTestConfig = newRangeTestConfig - } else { - fetchedNode[0].rangeTestConfig?.sender = Int32(config.sender) - fetchedNode[0].rangeTestConfig?.enabled = config.enabled - fetchedNode[0].rangeTestConfig?.save = config.save - } - do { - try context.save() - Logger.data.info("💾 [RangeTestConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [RangeTestConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [RangeTestConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Range Test Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [RangeTestConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum)) - MeshLogger.log("🤖 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Device Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].serialConfig == nil { - - let newSerialConfig = SerialConfigEntity(context: context) - newSerialConfig.enabled = config.enabled - newSerialConfig.echo = config.echo - newSerialConfig.rxd = Int32(config.rxd) - newSerialConfig.txd = Int32(config.txd) - newSerialConfig.baudRate = Int32(config.baud.rawValue) - newSerialConfig.timeout = Int32(config.timeout) - newSerialConfig.mode = Int32(config.mode.rawValue) - fetchedNode[0].serialConfig = newSerialConfig - - } else { - fetchedNode[0].serialConfig?.enabled = config.enabled - fetchedNode[0].serialConfig?.echo = config.echo - fetchedNode[0].serialConfig?.rxd = Int32(config.rxd) - fetchedNode[0].serialConfig?.txd = Int32(config.txd) - fetchedNode[0].serialConfig?.baudRate = Int32(config.baud.rawValue) - fetchedNode[0].serialConfig?.timeout = Int32(config.timeout) - fetchedNode[0].serialConfig?.mode = Int32(config.mode.rawValue) - } - - do { - try context.save() - Logger.data.info("💾 [SerialConfigEntity]Updated Serial Module Config for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - - context.rollback() - - let nsError = error as NSError - Logger.data.error("💥 [SerialConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [SerialConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Serial Module Config") - } - } catch { - - let nsError = error as NSError - Logger.data.error("💥 [SerialConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum)) - MeshLogger.log("📬 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Store & Forward Sensor Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].storeForwardConfig == nil { - - let newConfig = StoreForwardConfigEntity(context: context) - newConfig.enabled = config.enabled - newConfig.heartbeat = config.heartbeat - newConfig.records = Int32(config.records) - newConfig.historyReturnMax = Int32(config.historyReturnMax) - newConfig.historyReturnWindow = Int32(config.historyReturnWindow) - fetchedNode[0].storeForwardConfig = newConfig - - } else { - fetchedNode[0].storeForwardConfig?.enabled = config.enabled - fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat - fetchedNode[0].storeForwardConfig?.records = Int32(config.records) - fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax) - fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow) - } - do { - try context.save() - Logger.data.info("💾 [StoreForwardConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [StoreForwardConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - } else { - Logger.data.error("💥 [StoreForwardConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Store & Forward Module Config") - } - } catch { - let nsError = error as NSError - Logger.data.error("💥 [StoreForwardConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") - } -} - -func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum)) - MeshLogger.log("📈 \(logString)") - - let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - let fetchedNode = try context.fetch(fetchNodeInfoRequest) - // Found a node, save Telemetry Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].telemetryConfig == nil { - - let newTelemetryConfig = TelemetryConfigEntity(context: context) - newTelemetryConfig.deviceUpdateInterval = Int32(config.deviceUpdateInterval) - newTelemetryConfig.environmentUpdateInterval = Int32(config.environmentUpdateInterval) - newTelemetryConfig.environmentMeasurementEnabled = config.environmentMeasurementEnabled - newTelemetryConfig.environmentScreenEnabled = config.environmentScreenEnabled - newTelemetryConfig.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit - newTelemetryConfig.powerMeasurementEnabled = config.powerMeasurementEnabled - newTelemetryConfig.powerUpdateInterval = Int32(config.powerUpdateInterval) - newTelemetryConfig.powerScreenEnabled = config.powerScreenEnabled - fetchedNode[0].telemetryConfig = newTelemetryConfig - - } else { - fetchedNode[0].telemetryConfig?.deviceUpdateInterval = Int32(config.deviceUpdateInterval) - fetchedNode[0].telemetryConfig?.environmentUpdateInterval = Int32(config.environmentUpdateInterval) - fetchedNode[0].telemetryConfig?.environmentMeasurementEnabled = config.environmentMeasurementEnabled - fetchedNode[0].telemetryConfig?.environmentScreenEnabled = config.environmentScreenEnabled - fetchedNode[0].telemetryConfig?.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit - fetchedNode[0].telemetryConfig?.powerMeasurementEnabled = config.powerMeasurementEnabled - fetchedNode[0].telemetryConfig?.powerUpdateInterval = Int32(config.powerUpdateInterval) - fetchedNode[0].telemetryConfig?.powerScreenEnabled = config.powerScreenEnabled - } - - do { - try context.save() - Logger.data.info("💾 [TelemetryConfigEntity] Updated Telemetry Module Config for node: \(nodeNum.toHex(), privacy: .public)") - - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("💥 [TelemetryConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") - } - - } else { - Logger.data.error("💥 [TelemetryConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Telemetry Module Config") - } - - } catch { - let nsError = error as NSError - Logger.data.error("💥 [TelemetryConfigEntity] Fetching node for core data TelemetryConfigEntity failed: \(nsError, privacy: .public)") - } -} diff --git a/Meshtastic/Persistence/UpdateCoreDataController.swift b/Meshtastic/Persistence/UpdateCoreDataController.swift new file mode 100644 index 00000000..deb2944d --- /dev/null +++ b/Meshtastic/Persistence/UpdateCoreDataController.swift @@ -0,0 +1,1312 @@ +// +// UpdateCoreData.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 10/3/22. + +import CoreData +import MeshtasticProtobufs +import OSLog + +public class UpdateCoreDataController: ObservableObject { + + let context: NSManagedObjectContext + + init(context: NSManagedObjectContext) { + self.context = context + } + + public func clearPax(destNum: Int64) -> Bool { + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + let newPax = [PaxCounterLog]() + fetchedNode[0].pax? = NSOrderedSet(array: newPax) + do { + try context.save() + return true + + } catch { + context.rollback() + return false + } + } catch { + Logger.data.error("💥 [NodeInfoEntity] fetch data error") + return false + } + } + + public func clearPositions(destNum: Int64) -> Bool { + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + let newPostions = [PositionEntity]() + fetchedNode[0].positions? = NSOrderedSet(array: newPostions) + do { + try context.save() + return true + + } catch { + context.rollback() + return false + } + } catch { + Logger.data.error("💥 [NodeInfoEntity] fetch data error") + return false + } + } + + public func clearTelemetry(destNum: Int64, metricsType: Int32) -> Bool { + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + let emptyTelemetry = [TelemetryEntity]() + fetchedNode[0].telemetries? = NSOrderedSet(array: emptyTelemetry) + do { + try context.save() + return true + + } catch { + context.rollback() + return false + } + } catch { + Logger.data.error("💥 [NodeInfoEntity] fetch data error") + return false + } + } + + public func deleteChannelMessages(channel: ChannelEntity) { + do { + let objects = channel.allPrivateMessages + for object in objects { + context.delete(object) + } + try context.save() + } catch let error as NSError { + Logger.data.error("\(error.localizedDescription)") + } + } + + public func deleteUserMessages(user: UserEntity) { + + do { + let objects = user.messageList + for object in objects { + context.delete(object) + } + try context.save() + } catch let error as NSError { + Logger.data.error("\(error.localizedDescription)") + } + } + + func upsertNodeInfoPacket (packet: MeshPacket) { + + let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, packet.from.toHex()) + MeshLogger.log("📟 \(logString)") + + guard packet.from > 0 else { return } + + let fetchNodeInfoAppRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoAppRequest) + if fetchedNode.count == 0 { + // Not Found Insert + let newNode = NodeInfoEntity(context: context) + newNode.id = Int64(packet.from) + newNode.num = Int64(packet.from) + if packet.rxTime > 0 { + newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } + newNode.snr = packet.rxSnr + newNode.rssi = packet.rxRssi + newNode.viaMqtt = packet.viaMqtt + + if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { + newNode.channel = Int32(packet.channel) + } + if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { + newNode.hopsAway = Int32(nodeInfoMessage.hopsAway) + newNode.favorite = nodeInfoMessage.isFavorite + } + + if let newUserMessage = try? User(serializedData: packet.decoded.payload) { + + if newUserMessage.id.isEmpty { + if packet.from > Int16.max { + let newUser = createUser(num: Int64(packet.from), context: context) + newNode.user = newUser + } + } else { + + let newUser = UserEntity(context: context) + newUser.userId = newUserMessage.id + newUser.num = Int64(packet.from) + newUser.longName = newUserMessage.longName + newUser.shortName = newUserMessage.shortName + newUser.role = Int32(newUserMessage.role.rawValue) + newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() + newNode.user = newUser + + if UserDefaults.newNodeNotifications { + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: (UUID().uuidString), + title: "New Node", + subtitle: "\(newUser.longName ?? "unknown".localized)", + content: "New Node has been discovered", + target: "nodes", + path: "meshtastic://nodes?nodenum=\(newUser.num)" + ) + ] + manager.schedule() + } + } + } else { + if packet.from > Int16.max { + let newUser = createUser(num: Int64(packet.from), context: context) + newNode.user = newUser + } + } + + if newNode.user == nil && packet.from > Int16.max { + newNode.user = createUser(num: Int64(packet.from), context: context) + } + + let myInfoEntity = MyInfoEntity(context: context) + myInfoEntity.myNodeNum = Int64(packet.from) + myInfoEntity.rebootCount = 0 + do { + try context.save() + Logger.data.info("💾 [MyInfoEntity] Saved a new myInfo for node number: \(packet.from.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [MyInfoEntity] Error Inserting New Core Data: \(nsError, privacy: .public)") + } + newNode.myInfo = myInfoEntity + + } else { + // Update an existing node + fetchedNode[0].id = Int64(packet.from) + fetchedNode[0].num = Int64(packet.from) + if packet.rxTime > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + if fetchedNode[0].firstHeard == nil { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } + } + fetchedNode[0].snr = packet.rxSnr + fetchedNode[0].rssi = packet.rxRssi + fetchedNode[0].viaMqtt = packet.viaMqtt + if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { + fetchedNode[0].channel = Int32(packet.channel) + } + + if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { + + fetchedNode[0].hopsAway = Int32(nodeInfoMessage.hopsAway) + fetchedNode[0].favorite = nodeInfoMessage.isFavorite + if nodeInfoMessage.hasDeviceMetrics { + let telemetry = TelemetryEntity(context: context) + telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel) + telemetry.voltage = nodeInfoMessage.deviceMetrics.voltage + telemetry.channelUtilization = nodeInfoMessage.deviceMetrics.channelUtilization + telemetry.airUtilTx = nodeInfoMessage.deviceMetrics.airUtilTx + var newTelemetries = [TelemetryEntity]() + newTelemetries.append(telemetry) + fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries) + } + if nodeInfoMessage.hasUser { + /// Seeing Some crashes here ? + fetchedNode[0].user!.userId = nodeInfoMessage.user.id + fetchedNode[0].user!.num = Int64(nodeInfoMessage.num) + fetchedNode[0].user!.longName = nodeInfoMessage.user.longName + fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName + fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) + fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() + } + } else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart { + fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) + } + if fetchedNode[0].user == nil { + let newUser = createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + fetchedNode[0].user! = newUser + + } + do { + try context.save() + Logger.data.info("💾 [NodeInfoEntity] Updated from Node Info App Packet For: \(fetchedNode[0].num.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [NodeInfoEntity] Error Saving from NODEINFO_APP \(nsError, privacy: .public)") + } + } + } catch { + Logger.data.error("💥 [NodeInfoEntity] fetch data error for NODEINFO_APP") + } + } + + func upsertPositionPacket (packet: MeshPacket) { + + let logString = String.localizedStringWithFormat("mesh.log.position.received %@".localized, String(packet.from)) + MeshLogger.log("📍 \(logString)") + + let fetchNodePositionRequest = NodeInfoEntity.fetchRequest() + fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + + if let positionMessage = try? Position(serializedData: packet.decoded.payload) { + + /// Don't save empty position packets from null island or apple park + if (positionMessage.longitudeI != 0 && positionMessage.latitudeI != 0) && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) { + let fetchedNode = try context.fetch(fetchNodePositionRequest) + if fetchedNode.count == 1 { + + // Unset the current latest position for this node + let fetchCurrentLatestPositionsRequest = PositionEntity.fetchRequest() + fetchCurrentLatestPositionsRequest.predicate = NSPredicate(format: "nodePosition.num == %lld && latest = true", Int64(packet.from)) + + let fetchedPositions = try context.fetch(fetchCurrentLatestPositionsRequest) + if fetchedPositions.count > 0 { + for position in fetchedPositions { + position.latest = false + } + } + let position = PositionEntity(context: context) + position.latest = true + position.snr = packet.rxSnr + position.rssi = packet.rxRssi + position.seqNo = Int32(positionMessage.seqNumber) + position.latitudeI = positionMessage.latitudeI + position.longitudeI = positionMessage.longitudeI + position.altitude = positionMessage.altitude + position.satsInView = Int32(positionMessage.satsInView) + position.speed = Int32(positionMessage.groundSpeed) + let heading = Int32(positionMessage.groundTrack) + // Throw out bad haeadings from the device + if heading >= 0 && heading <= 360 { + position.heading = Int32(positionMessage.groundTrack) + } + position.precisionBits = Int32(positionMessage.precisionBits) + if positionMessage.timestamp != 0 { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp))) + } else { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + } + guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { + return + } + /// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one. + if mutablePositions.count > 0 && (position.precisionBits == 32 || position.precisionBits == 0) { + if let mostRecent = mutablePositions.lastObject as? PositionEntity, mostRecent.coordinate.distance(from: position.coordinate) < 15.0 { + mutablePositions.remove(mostRecent) + } + } else if mutablePositions.count > 0 { + /// Don't store any history for reduced accuracy positions, we will just show a circle + mutablePositions.removeAllObjects() + } + mutablePositions.add(position) + fetchedNode[0].id = Int64(packet.from) + fetchedNode[0].num = Int64(packet.from) + if positionMessage.time > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + } else if packet.rxTime > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } + fetchedNode[0].snr = packet.rxSnr + fetchedNode[0].rssi = packet.rxRssi + fetchedNode[0].viaMqtt = packet.viaMqtt + fetchedNode[0].channel = Int32(packet.channel) + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + + do { + try context.save() + Logger.data.info("💾 [Position] Saved from Position App Packet For: \(fetchedNode[0].num.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError, privacy: .public)") + } + } + } else { + + if (try? NodeInfo(serializedData: packet.decoded.payload)) != nil { + upsertNodeInfoPacket(packet: packet) + } else { + Logger.data.error("💥 Empty POSITION_APP Packet: \((try? packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") + } + } + } + } catch { + Logger.data.error("💥 Error Deserializing POSITION_APP packet.") + } + } + + func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum)) + MeshLogger.log("📶 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Device Config + if !fetchedNode.isEmpty { + if fetchedNode[0].bluetoothConfig == nil { + let newBluetoothConfig = BluetoothConfigEntity(context: context) + newBluetoothConfig.enabled = config.enabled + newBluetoothConfig.mode = Int32(config.mode.rawValue) + newBluetoothConfig.fixedPin = Int32(config.fixedPin) + newBluetoothConfig.deviceLoggingEnabled = config.deviceLoggingEnabled + fetchedNode[0].bluetoothConfig = newBluetoothConfig + } else { + fetchedNode[0].bluetoothConfig?.enabled = config.enabled + fetchedNode[0].bluetoothConfig?.mode = Int32(config.mode.rawValue) + fetchedNode[0].bluetoothConfig?.fixedPin = Int32(config.fixedPin) + fetchedNode[0].bluetoothConfig?.deviceLoggingEnabled = config.deviceLoggingEnabled + } + do { + try context.save() + Logger.data.info("💾 [BluetoothConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [BluetoothConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [BluetoothConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Bluetooth Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [BluetoothConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum)) + MeshLogger.log("📟 \(logString)") + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Device Config + if !fetchedNode.isEmpty { + if fetchedNode[0].deviceConfig == nil { + let newDeviceConfig = DeviceConfigEntity(context: context) + newDeviceConfig.role = Int32(config.role.rawValue) + newDeviceConfig.serialEnabled = config.serialEnabled + newDeviceConfig.debugLogEnabled = config.debugLogEnabled + newDeviceConfig.buttonGpio = Int32(config.buttonGpio) + newDeviceConfig.buzzerGpio = Int32(config.buzzerGpio) + newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue) + newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) + newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress + newDeviceConfig.ledHeartbeatEnabled = !config.ledHeartbeatDisabled + newDeviceConfig.isManaged = config.isManaged + newDeviceConfig.tzdef = config.tzdef + fetchedNode[0].deviceConfig = newDeviceConfig + } else { + fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue) + fetchedNode[0].deviceConfig?.serialEnabled = config.serialEnabled + fetchedNode[0].deviceConfig?.debugLogEnabled = config.debugLogEnabled + fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.buttonGpio) + fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.buzzerGpio) + fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue) + fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) + fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress + fetchedNode[0].deviceConfig?.ledHeartbeatEnabled = !config.ledHeartbeatDisabled + fetchedNode[0].deviceConfig?.isManaged = config.isManaged + fetchedNode[0].deviceConfig?.tzdef = config.tzdef + } + do { + try context.save() + Logger.data.info("💾 [DeviceConfigEntity] Updated Device Config for node number: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [DeviceConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [DeviceConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex()) + MeshLogger.log("🖥️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Device Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].displayConfig == nil { + + let newDisplayConfig = DisplayConfigEntity(context: context) + newDisplayConfig.gpsFormat = Int32(config.gpsFormat.rawValue) + newDisplayConfig.screenOnSeconds = Int32(truncatingIfNeeded: config.screenOnSecs) + newDisplayConfig.screenCarouselInterval = Int32(truncatingIfNeeded: config.autoScreenCarouselSecs) + newDisplayConfig.compassNorthTop = config.compassNorthTop + newDisplayConfig.flipScreen = config.flipScreen + newDisplayConfig.oledType = Int32(config.oled.rawValue) + newDisplayConfig.displayMode = Int32(config.displaymode.rawValue) + newDisplayConfig.units = Int32(config.units.rawValue) + newDisplayConfig.headingBold = config.headingBold + fetchedNode[0].displayConfig = newDisplayConfig + + } else { + + fetchedNode[0].displayConfig?.gpsFormat = Int32(config.gpsFormat.rawValue) + fetchedNode[0].displayConfig?.screenOnSeconds = Int32(truncatingIfNeeded: config.screenOnSecs) + fetchedNode[0].displayConfig?.screenCarouselInterval = Int32(truncatingIfNeeded: config.autoScreenCarouselSecs) + fetchedNode[0].displayConfig?.compassNorthTop = config.compassNorthTop + fetchedNode[0].displayConfig?.flipScreen = config.flipScreen + fetchedNode[0].displayConfig?.oledType = Int32(config.oled.rawValue) + fetchedNode[0].displayConfig?.displayMode = Int32(config.displaymode.rawValue) + fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue) + fetchedNode[0].displayConfig?.headingBold = config.headingBold + } + + do { + + try context.save() + Logger.data.info("💾 [DisplayConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + + } catch { + + context.rollback() + + let nsError = error as NSError + Logger.data.error("💥 [DisplayConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + + Logger.data.error("💥 [DisplayConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex()) unable to save Display Config") + } + + } catch { + + let nsError = error as NSError + Logger.data.error("💥 [DisplayConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex()) + MeshLogger.log("📻 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", nodeNum) + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save LoRa Config + if fetchedNode.count > 0 { + if fetchedNode[0].loRaConfig == nil { + // No lora config for node, save a new lora config + let newLoRaConfig = LoRaConfigEntity(context: context) + newLoRaConfig.regionCode = Int32(config.region.rawValue) + newLoRaConfig.usePreset = config.usePreset + newLoRaConfig.modemPreset = Int32(config.modemPreset.rawValue) + newLoRaConfig.bandwidth = Int32(config.bandwidth) + newLoRaConfig.spreadFactor = Int32(config.spreadFactor) + newLoRaConfig.codingRate = Int32(config.codingRate) + newLoRaConfig.frequencyOffset = config.frequencyOffset + newLoRaConfig.overrideFrequency = config.overrideFrequency + newLoRaConfig.overrideDutyCycle = config.overrideDutyCycle + newLoRaConfig.hopLimit = Int32(config.hopLimit) + newLoRaConfig.txPower = Int32(config.txPower) + newLoRaConfig.txEnabled = config.txEnabled + newLoRaConfig.channelNum = Int32(config.channelNum) + newLoRaConfig.sx126xRxBoostedGain = config.sx126XRxBoostedGain + newLoRaConfig.ignoreMqtt = config.ignoreMqtt + fetchedNode[0].loRaConfig = newLoRaConfig + } else { + fetchedNode[0].loRaConfig?.regionCode = Int32(config.region.rawValue) + fetchedNode[0].loRaConfig?.usePreset = config.usePreset + fetchedNode[0].loRaConfig?.modemPreset = Int32(config.modemPreset.rawValue) + fetchedNode[0].loRaConfig?.bandwidth = Int32(config.bandwidth) + fetchedNode[0].loRaConfig?.spreadFactor = Int32(config.spreadFactor) + fetchedNode[0].loRaConfig?.codingRate = Int32(config.codingRate) + fetchedNode[0].loRaConfig?.frequencyOffset = config.frequencyOffset + fetchedNode[0].loRaConfig?.overrideFrequency = config.overrideFrequency + fetchedNode[0].loRaConfig?.overrideDutyCycle = config.overrideDutyCycle + fetchedNode[0].loRaConfig?.hopLimit = Int32(config.hopLimit) + fetchedNode[0].loRaConfig?.txPower = Int32(config.txPower) + fetchedNode[0].loRaConfig?.txEnabled = config.txEnabled + fetchedNode[0].loRaConfig?.channelNum = Int32(config.channelNum) + fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain + fetchedNode[0].loRaConfig?.ignoreMqtt = config.ignoreMqtt + fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain + } + do { + try context.save() + Logger.data.info("💾 [LoRaConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [LoRaConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [LoRaConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Lora Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [LoRaConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum)) + MeshLogger.log("🌐 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save WiFi Config + if !fetchedNode.isEmpty { + if fetchedNode[0].networkConfig == nil { + let newNetworkConfig = NetworkConfigEntity(context: context) + newNetworkConfig.wifiEnabled = config.wifiEnabled + newNetworkConfig.wifiSsid = config.wifiSsid + newNetworkConfig.wifiPsk = config.wifiPsk + newNetworkConfig.ethEnabled = config.ethEnabled + fetchedNode[0].networkConfig = newNetworkConfig + } else { + fetchedNode[0].networkConfig?.ethEnabled = config.ethEnabled + fetchedNode[0].networkConfig?.wifiEnabled = config.wifiEnabled + fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid + fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk + } + + do { + try context.save() + Logger.data.info("💾 [NetworkConfigEntity] Updated Network Config for node: \(nodeNum.toHex(), privacy: .public)") + + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [NetworkConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [NetworkConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Network Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [NetworkConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum)) + MeshLogger.log("🗺️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save LoRa Config + if !fetchedNode.isEmpty { + if fetchedNode[0].positionConfig == nil { + let newPositionConfig = PositionConfigEntity(context: context) + newPositionConfig.smartPositionEnabled = config.positionBroadcastSmartEnabled + newPositionConfig.deviceGpsEnabled = config.gpsEnabled + newPositionConfig.gpsMode = Int32(config.gpsMode.rawValue) + newPositionConfig.rxGpio = Int32(config.rxGpio) + newPositionConfig.txGpio = Int32(config.txGpio) + newPositionConfig.gpsEnGpio = Int32(config.gpsEnGpio) + newPositionConfig.fixedPosition = config.fixedPosition + newPositionConfig.positionBroadcastSeconds = Int32(truncatingIfNeeded: config.positionBroadcastSecs) + newPositionConfig.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs) + newPositionConfig.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance) + newPositionConfig.positionFlags = Int32(config.positionFlags) + newPositionConfig.gpsAttemptTime = 900 + newPositionConfig.gpsUpdateInterval = Int32(config.gpsUpdateInterval) + fetchedNode[0].positionConfig = newPositionConfig + } else { + fetchedNode[0].positionConfig?.smartPositionEnabled = config.positionBroadcastSmartEnabled + fetchedNode[0].positionConfig?.deviceGpsEnabled = config.gpsEnabled + fetchedNode[0].positionConfig?.gpsMode = Int32(config.gpsMode.rawValue) + fetchedNode[0].positionConfig?.rxGpio = Int32(config.rxGpio) + fetchedNode[0].positionConfig?.txGpio = Int32(config.txGpio) + fetchedNode[0].positionConfig?.gpsEnGpio = Int32(config.gpsEnGpio) + fetchedNode[0].positionConfig?.fixedPosition = config.fixedPosition + fetchedNode[0].positionConfig?.positionBroadcastSeconds = Int32(truncatingIfNeeded: config.positionBroadcastSecs) + fetchedNode[0].positionConfig?.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs) + fetchedNode[0].positionConfig?.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance) + fetchedNode[0].positionConfig?.gpsAttemptTime = 900 + fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.gpsUpdateInterval) + fetchedNode[0].positionConfig?.positionFlags = Int32(config.positionFlags) + } + do { + try context.save() + Logger.data.info("💾 [PositionConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [PositionConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [PositionConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Position Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [PositionConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64) { + let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum)) + MeshLogger.log("🗺️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Power Config + if !fetchedNode.isEmpty { + if fetchedNode[0].powerConfig == nil { + let newPowerConfig = PowerConfigEntity(context: context) + newPowerConfig.adcMultiplierOverride = config.adcMultiplierOverride + newPowerConfig.deviceBatteryInaAddress = Int32(config.deviceBatteryInaAddress) + newPowerConfig.isPowerSaving = config.isPowerSaving + newPowerConfig.lsSecs = Int32(truncatingIfNeeded: config.lsSecs) + newPowerConfig.minWakeSecs = Int32(truncatingIfNeeded: config.minWakeSecs) + newPowerConfig.onBatteryShutdownAfterSecs = Int32(truncatingIfNeeded: config.onBatteryShutdownAfterSecs) + newPowerConfig.waitBluetoothSecs = Int32(truncatingIfNeeded: config.waitBluetoothSecs) + fetchedNode[0].powerConfig = newPowerConfig + } else { + fetchedNode[0].powerConfig?.adcMultiplierOverride = config.adcMultiplierOverride + fetchedNode[0].powerConfig?.deviceBatteryInaAddress = Int32(config.deviceBatteryInaAddress) + fetchedNode[0].powerConfig?.isPowerSaving = config.isPowerSaving + fetchedNode[0].powerConfig?.lsSecs = Int32(truncatingIfNeeded: config.lsSecs) + fetchedNode[0].powerConfig?.minWakeSecs = Int32(truncatingIfNeeded: config.minWakeSecs) + fetchedNode[0].powerConfig?.onBatteryShutdownAfterSecs = Int32(truncatingIfNeeded: config.onBatteryShutdownAfterSecs) + fetchedNode[0].powerConfig?.waitBluetoothSecs = Int32(truncatingIfNeeded: config.waitBluetoothSecs) + } + do { + try context.save() + Logger.data.info("💾 [PowerConfigEntity] Updated Power Config for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [PowerConfigEntity] Error Updating Core Data PowerConfigEntity: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [PowerConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Power Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [PowerConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum)) + MeshLogger.log("🏮 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Ambient Lighting Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].cannedMessageConfig == nil { + + let newAmbientLightingConfig = AmbientLightingConfigEntity(context: context) + + newAmbientLightingConfig.ledState = config.ledState + newAmbientLightingConfig.current = Int32(config.current) + newAmbientLightingConfig.red = Int32(config.red) + newAmbientLightingConfig.green = Int32(config.green) + newAmbientLightingConfig.blue = Int32(config.blue) + fetchedNode[0].ambientLightingConfig = newAmbientLightingConfig + + } else { + + if fetchedNode[0].ambientLightingConfig == nil { + fetchedNode[0].ambientLightingConfig = AmbientLightingConfigEntity(context: context) + } + fetchedNode[0].ambientLightingConfig?.ledState = config.ledState + fetchedNode[0].ambientLightingConfig?.current = Int32(config.current) + fetchedNode[0].ambientLightingConfig?.red = Int32(config.red) + fetchedNode[0].ambientLightingConfig?.green = Int32(config.green) + fetchedNode[0].ambientLightingConfig?.blue = Int32(config.blue) + } + + do { + try context.save() + Logger.data.info("💾 [AmbientLightingConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [AmbientLightingConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [AmbientLightingConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Ambient Lighting Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [AmbientLightingConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum)) + MeshLogger.log("🥫 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Canned Message Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].cannedMessageConfig == nil { + + let newCannedMessageConfig = CannedMessageConfigEntity(context: context) + + newCannedMessageConfig.enabled = config.enabled + newCannedMessageConfig.sendBell = config.sendBell + newCannedMessageConfig.rotary1Enabled = config.rotary1Enabled + newCannedMessageConfig.updown1Enabled = config.updown1Enabled + newCannedMessageConfig.inputbrokerPinA = Int32(config.inputbrokerPinA) + newCannedMessageConfig.inputbrokerPinB = Int32(config.inputbrokerPinB) + newCannedMessageConfig.inputbrokerPinPress = Int32(config.inputbrokerPinPress) + newCannedMessageConfig.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue) + newCannedMessageConfig.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue) + newCannedMessageConfig.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue) + + fetchedNode[0].cannedMessageConfig = newCannedMessageConfig + + } else { + + fetchedNode[0].cannedMessageConfig?.enabled = config.enabled + fetchedNode[0].cannedMessageConfig?.sendBell = config.sendBell + fetchedNode[0].cannedMessageConfig?.rotary1Enabled = config.rotary1Enabled + fetchedNode[0].cannedMessageConfig?.updown1Enabled = config.updown1Enabled + fetchedNode[0].cannedMessageConfig?.inputbrokerPinA = Int32(config.inputbrokerPinA) + fetchedNode[0].cannedMessageConfig?.inputbrokerPinB = Int32(config.inputbrokerPinB) + fetchedNode[0].cannedMessageConfig?.inputbrokerPinPress = Int32(config.inputbrokerPinPress) + fetchedNode[0].cannedMessageConfig?.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue) + fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue) + fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue) + } + + do { + try context.save() + Logger.data.info("💾 [CannedMessageConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [CannedMessageConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [CannedMessageConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Canned Message Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [CannedMessageConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) + MeshLogger.log("🕵️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Detection Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].detectionSensorConfig == nil { + + let newConfig = DetectionSensorConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.sendBell = config.sendBell + newConfig.name = config.name + + newConfig.monitorPin = Int32(config.monitorPin) + newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh + newConfig.usePullup = config.usePullup + newConfig.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) + newConfig.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) + fetchedNode[0].detectionSensorConfig = newConfig + + } else { + fetchedNode[0].detectionSensorConfig?.enabled = config.enabled + fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell + fetchedNode[0].detectionSensorConfig?.name = config.name + fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) + fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup + fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh + fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) + fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) + } + + do { + try context.save() + Logger.data.info("💾 [DetectionSensorConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [DetectionSensorConfigEntity] Error Updating Core Data : \(nsError, privacy: .public)") + } + + } else { + Logger.data.error("💥 [DetectionSensorConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Detection Sensor Module Config") + } + + } catch { + let nsError = error as NSError + Logger.data.error("💥 [DetectionSensorConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum)) + MeshLogger.log("📣 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save External Notificaitone Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].externalNotificationConfig == nil { + let newExternalNotificationConfig = ExternalNotificationConfigEntity(context: context) + newExternalNotificationConfig.enabled = config.enabled + newExternalNotificationConfig.usePWM = config.usePwm + newExternalNotificationConfig.alertBell = config.alertBell + newExternalNotificationConfig.alertBellBuzzer = config.alertBellBuzzer + newExternalNotificationConfig.alertBellVibra = config.alertBellVibra + newExternalNotificationConfig.alertMessage = config.alertMessage + newExternalNotificationConfig.alertMessageBuzzer = config.alertMessageBuzzer + newExternalNotificationConfig.alertMessageVibra = config.alertMessageVibra + newExternalNotificationConfig.active = config.active + newExternalNotificationConfig.output = Int32(config.output) + newExternalNotificationConfig.outputBuzzer = Int32(config.outputBuzzer) + newExternalNotificationConfig.outputVibra = Int32(config.outputVibra) + newExternalNotificationConfig.outputMilliseconds = Int32(config.outputMs) + newExternalNotificationConfig.nagTimeout = Int32(config.nagTimeout) + newExternalNotificationConfig.useI2SAsBuzzer = config.useI2SAsBuzzer + fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig + + } else { + fetchedNode[0].externalNotificationConfig?.enabled = config.enabled + fetchedNode[0].externalNotificationConfig?.usePWM = config.usePwm + fetchedNode[0].externalNotificationConfig?.alertBell = config.alertBell + fetchedNode[0].externalNotificationConfig?.alertBellBuzzer = config.alertBellBuzzer + fetchedNode[0].externalNotificationConfig?.alertBellVibra = config.alertBellVibra + fetchedNode[0].externalNotificationConfig?.alertMessage = config.alertMessage + fetchedNode[0].externalNotificationConfig?.alertMessageBuzzer = config.alertMessageBuzzer + fetchedNode[0].externalNotificationConfig?.alertMessageVibra = config.alertMessageVibra + fetchedNode[0].externalNotificationConfig?.active = config.active + fetchedNode[0].externalNotificationConfig?.output = Int32(config.output) + fetchedNode[0].externalNotificationConfig?.outputBuzzer = Int32(config.outputBuzzer) + fetchedNode[0].externalNotificationConfig?.outputVibra = Int32(config.outputVibra) + fetchedNode[0].externalNotificationConfig?.outputMilliseconds = Int32(config.outputMs) + fetchedNode[0].externalNotificationConfig?.nagTimeout = Int32(config.nagTimeout) + fetchedNode[0].externalNotificationConfig?.useI2SAsBuzzer = config.useI2SAsBuzzer + } + + do { + try context.save() + Logger.data.info("💾 [ExternalNotificationConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [ExternalNotificationConfigEntity] Error Updating Core Data : \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [ExternalNotificationConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save External Notification Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [ExternalNotificationConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.paxcounter.config %@".localized, String(nodeNum)) + MeshLogger.log("🧑‍🤝‍🧑 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save PAX Counter Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].paxCounterConfig == nil { + let newPaxCounterConfig = PaxCounterConfigEntity(context: context) + newPaxCounterConfig.enabled = config.enabled + newPaxCounterConfig.updateInterval = Int32(config.paxcounterUpdateInterval) + + fetchedNode[0].paxCounterConfig = newPaxCounterConfig + + } else { + fetchedNode[0].paxCounterConfig?.enabled = config.enabled + fetchedNode[0].paxCounterConfig?.updateInterval = Int32(config.paxcounterUpdateInterval) + } + + do { + try context.save() + Logger.data.info("💾 [PaxCounterConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [PaxCounterConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [PaxCounterConfigEntity] No Nodes found in local database matching node number \(nodeNum.toHex(), privacy: .public) unable to save PAX Counter Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [PaxCounterConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum)) + MeshLogger.log("⛰️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save RTTTL Config + if !fetchedNode.isEmpty { + if fetchedNode[0].rtttlConfig == nil { + let newRtttlConfig = RTTTLConfigEntity(context: context) + newRtttlConfig.ringtone = ringtone + fetchedNode[0].rtttlConfig = newRtttlConfig + } else { + fetchedNode[0].rtttlConfig?.ringtone = ringtone + } + do { + try context.save() + Logger.data.info("💾 [RtttlConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [RtttlConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [RtttlConfigEntity] No nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save RTTTL Ringtone Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [RtttlConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum)) + MeshLogger.log("🌉 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save MQTT Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].mqttConfig == nil { + let newMQTTConfig = MQTTConfigEntity(context: context) + newMQTTConfig.enabled = config.enabled + newMQTTConfig.proxyToClientEnabled = config.proxyToClientEnabled + newMQTTConfig.address = config.address + newMQTTConfig.username = config.username + newMQTTConfig.password = config.password + newMQTTConfig.root = config.root + newMQTTConfig.encryptionEnabled = config.encryptionEnabled + newMQTTConfig.jsonEnabled = config.jsonEnabled + newMQTTConfig.tlsEnabled = config.tlsEnabled + newMQTTConfig.mapReportingEnabled = config.mapReportingEnabled + newMQTTConfig.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) + newMQTTConfig.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) + fetchedNode[0].mqttConfig = newMQTTConfig + } else { + fetchedNode[0].mqttConfig?.enabled = config.enabled + fetchedNode[0].mqttConfig?.proxyToClientEnabled = config.proxyToClientEnabled + fetchedNode[0].mqttConfig?.address = config.address + fetchedNode[0].mqttConfig?.username = config.username + fetchedNode[0].mqttConfig?.password = config.password + fetchedNode[0].mqttConfig?.root = config.root + fetchedNode[0].mqttConfig?.encryptionEnabled = config.encryptionEnabled + fetchedNode[0].mqttConfig?.jsonEnabled = config.jsonEnabled + fetchedNode[0].mqttConfig?.tlsEnabled = config.tlsEnabled + fetchedNode[0].mqttConfig?.mapReportingEnabled = config.mapReportingEnabled + fetchedNode[0].mqttConfig?.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) + fetchedNode[0].mqttConfig?.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) + } + do { + try context.save() + Logger.data.info("💾 [MQTTConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [MQTTConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [MQTTConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save MQTT Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [MQTTConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum)) + MeshLogger.log("⛰️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Device Config + if !fetchedNode.isEmpty { + if fetchedNode[0].rangeTestConfig == nil { + let newRangeTestConfig = RangeTestConfigEntity(context: context) + newRangeTestConfig.sender = Int32(config.sender) + newRangeTestConfig.enabled = config.enabled + newRangeTestConfig.save = config.save + fetchedNode[0].rangeTestConfig = newRangeTestConfig + } else { + fetchedNode[0].rangeTestConfig?.sender = Int32(config.sender) + fetchedNode[0].rangeTestConfig?.enabled = config.enabled + fetchedNode[0].rangeTestConfig?.save = config.save + } + do { + try context.save() + Logger.data.info("💾 [RangeTestConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [RangeTestConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [RangeTestConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Range Test Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [RangeTestConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum)) + MeshLogger.log("🤖 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Device Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].serialConfig == nil { + + let newSerialConfig = SerialConfigEntity(context: context) + newSerialConfig.enabled = config.enabled + newSerialConfig.echo = config.echo + newSerialConfig.rxd = Int32(config.rxd) + newSerialConfig.txd = Int32(config.txd) + newSerialConfig.baudRate = Int32(config.baud.rawValue) + newSerialConfig.timeout = Int32(config.timeout) + newSerialConfig.mode = Int32(config.mode.rawValue) + fetchedNode[0].serialConfig = newSerialConfig + + } else { + fetchedNode[0].serialConfig?.enabled = config.enabled + fetchedNode[0].serialConfig?.echo = config.echo + fetchedNode[0].serialConfig?.rxd = Int32(config.rxd) + fetchedNode[0].serialConfig?.txd = Int32(config.txd) + fetchedNode[0].serialConfig?.baudRate = Int32(config.baud.rawValue) + fetchedNode[0].serialConfig?.timeout = Int32(config.timeout) + fetchedNode[0].serialConfig?.mode = Int32(config.mode.rawValue) + } + + do { + try context.save() + Logger.data.info("💾 [SerialConfigEntity]Updated Serial Module Config for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + + context.rollback() + + let nsError = error as NSError + Logger.data.error("💥 [SerialConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [SerialConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Serial Module Config") + } + } catch { + + let nsError = error as NSError + Logger.data.error("💥 [SerialConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum)) + MeshLogger.log("📬 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Store & Forward Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].storeForwardConfig == nil { + + let newConfig = StoreForwardConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.heartbeat = config.heartbeat + newConfig.records = Int32(config.records) + newConfig.historyReturnMax = Int32(config.historyReturnMax) + newConfig.historyReturnWindow = Int32(config.historyReturnWindow) + fetchedNode[0].storeForwardConfig = newConfig + + } else { + fetchedNode[0].storeForwardConfig?.enabled = config.enabled + fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat + fetchedNode[0].storeForwardConfig?.records = Int32(config.records) + fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax) + fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow) + } + do { + try context.save() + Logger.data.info("💾 [StoreForwardConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [StoreForwardConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [StoreForwardConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Store & Forward Module Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [StoreForwardConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } + } + + func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64) { + + let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum)) + MeshLogger.log("📈 \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Telemetry Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].telemetryConfig == nil { + + let newTelemetryConfig = TelemetryConfigEntity(context: context) + newTelemetryConfig.deviceUpdateInterval = Int32(config.deviceUpdateInterval) + newTelemetryConfig.environmentUpdateInterval = Int32(config.environmentUpdateInterval) + newTelemetryConfig.environmentMeasurementEnabled = config.environmentMeasurementEnabled + newTelemetryConfig.environmentScreenEnabled = config.environmentScreenEnabled + newTelemetryConfig.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit + newTelemetryConfig.powerMeasurementEnabled = config.powerMeasurementEnabled + newTelemetryConfig.powerUpdateInterval = Int32(config.powerUpdateInterval) + newTelemetryConfig.powerScreenEnabled = config.powerScreenEnabled + fetchedNode[0].telemetryConfig = newTelemetryConfig + + } else { + fetchedNode[0].telemetryConfig?.deviceUpdateInterval = Int32(config.deviceUpdateInterval) + fetchedNode[0].telemetryConfig?.environmentUpdateInterval = Int32(config.environmentUpdateInterval) + fetchedNode[0].telemetryConfig?.environmentMeasurementEnabled = config.environmentMeasurementEnabled + fetchedNode[0].telemetryConfig?.environmentScreenEnabled = config.environmentScreenEnabled + fetchedNode[0].telemetryConfig?.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit + fetchedNode[0].telemetryConfig?.powerMeasurementEnabled = config.powerMeasurementEnabled + fetchedNode[0].telemetryConfig?.powerUpdateInterval = Int32(config.powerUpdateInterval) + fetchedNode[0].telemetryConfig?.powerScreenEnabled = config.powerScreenEnabled + } + + do { + try context.save() + Logger.data.info("💾 [TelemetryConfigEntity] Updated Telemetry Module Config for node: \(nodeNum.toHex(), privacy: .public)") + + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [TelemetryConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + + } else { + Logger.data.error("💥 [TelemetryConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Telemetry Module Config") + } + + } catch { + let nsError = error as NSError + Logger.data.error("💥 [TelemetryConfigEntity] Fetching node for core data TelemetryConfigEntity failed: \(nsError, privacy: .public)") + } + } +} diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index c3745ebd..bdaf32e9 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -226,7 +226,8 @@ struct Connect: View { } catch { Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)") } - clearCoreDataDatabase(context: context, includeRoutes: false) + // TODO: fix-me +// clearCoreDataDatabase(context: context, includeRoutes: false) } UserDefaults.preferredPeripheralId = selectedPeripherialId self.bleManager.connectTo(peripheral: peripheral.peripheral) @@ -323,11 +324,6 @@ struct Connect: View { } } } - .onAppear(perform: { - if self.bleManager.context == nil { - self.bleManager.context = context - } - }) } #if canImport(ActivityKit) func startNodeActivity() { diff --git a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift index 8217af2d..b42b1b4b 100644 --- a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift +++ b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift @@ -13,6 +13,8 @@ import SwiftUI struct WaypointFormMapKit: View { @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController + @Environment(\.managedObjectContext) private var context @Environment(\.dismiss) private var dismiss @State var coordinate: WaypointCoordinate @FocusState private var iconIsFocused: Bool @@ -122,7 +124,7 @@ struct WaypointFormMapKit: View { // Loading a waypoint from edit if coordinate.waypointId > 0 { newWaypoint.id = UInt32(coordinate.waypointId) - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) + let waypoint = queryCoreDataController.getWaypoint(id: Int64(coordinate.waypointId)) newWaypoint.latitudeI = waypoint.latitudeI newWaypoint.longitudeI = waypoint.longitudeI } else { @@ -179,14 +181,15 @@ struct WaypointFormMapKit: View { Menu { Button("For me", action: { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) - bleManager.context!.delete(waypoint) - do { - try bleManager.context!.save() - } catch { - bleManager.context!.rollback() - } - dismiss() }) + let waypoint = queryCoreDataController.getWaypoint(id: Int64(coordinate.waypointId)) + context.delete(waypoint) + do { + try context.save() + } catch { + context.rollback() + } + dismiss() + }) Button("For everyone", action: { var newWaypoint = Waypoint() @@ -230,7 +233,7 @@ struct WaypointFormMapKit: View { } .onAppear { if coordinate.waypointId > 0 { - let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!) + let waypoint = queryCoreDataController.getWaypoint(id: Int64(coordinate.waypointId)) name = waypoint.name ?? "Dropped Pin" description = waypoint.longDescription ?? "" icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index c4957bbc..7de6d87f 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -14,6 +14,7 @@ struct ChannelList: View { @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var updateCoreDataController: UpdateCoreDataController @State var node: NodeInfoEntity? @@ -140,7 +141,7 @@ struct ChannelList: View { titleVisibility: .visible ) { Button(role: .destructive) { - deleteChannelMessages(channel: channelSelection!, context: context) + updateCoreDataController.deleteChannelMessages(channel: channelSelection!) context.refresh(myInfo, mergeChanges: true) UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages channelSelection = nil @@ -148,11 +149,6 @@ struct ChannelList: View { Text("delete") } } - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } .padding([.top, .bottom]) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 882dad07..6ba5191f 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -131,9 +131,6 @@ struct ChannelMessageList: View { .padding([.top]) .scrollDismissesKeyboard(.immediately) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } if channel.allPrivateMessages.count > 0 { scrollView.scrollTo(channel.allPrivateMessages.last!.messageId) } diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 35206d7c..dbab3636 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -87,9 +87,6 @@ struct Messages: View { } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } if UserDefaults.preferredPeripheralId.count > 0 { let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(UserDefaults.preferredPeripheralNum)) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index b0c9beb0..2c85cb66 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -17,6 +17,7 @@ struct UserList: View { @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var updateCoreDataController: UpdateCoreDataController @State private var searchText = "" @State private var viaLora = true @State private var viaMqtt = true @@ -158,7 +159,7 @@ struct UserList: View { titleVisibility: .visible ) { Button(role: .destructive) { - deleteUserMessages(user: userSelection!, context: context) + updateCoreDataController.deleteUserMessages(user: userSelection!) context.refresh(node!.user!, mergeChanges: true) UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages } label: { @@ -210,9 +211,6 @@ struct UserList: View { userSelection = users.first(where: { $0.num == newUserNum }) } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } searchUserList() } .safeAreaInset(edge: .bottom, alignment: .trailing) { diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 41c83f51..c5a91a99 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -116,9 +116,6 @@ struct UserMessageList: View { .padding([.top]) .scrollDismissesKeyboard(.immediately) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } if user.messageList.count > 0 { scrollView.scrollTo(user.messageList.last!.messageId) } diff --git a/Meshtastic/Views/Nodes/DetectionSensorLog.swift b/Meshtastic/Views/Nodes/DetectionSensorLog.swift index da2e4e09..06c468ca 100644 --- a/Meshtastic/Views/Nodes/DetectionSensorLog.swift +++ b/Meshtastic/Views/Nodes/DetectionSensorLog.swift @@ -124,11 +124,6 @@ struct DetectionSensorLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 396b6098..4440ead8 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -12,6 +12,7 @@ struct DeviceMetricsLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var updateCoreDataController: UpdateCoreDataController @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @@ -188,7 +189,7 @@ struct DeviceMetricsLog: View { titleVisibility: .visible ) { Button("device.metrics.delete", role: .destructive) { - if clearTelemetry(destNum: node.num, metricsType: 0, context: context) { + if updateCoreDataController.clearTelemetry(destNum: node.num, metricsType: 0) { Logger.data.notice("Cleared Device Metrics for \(node.num)") } else { Logger.data.error("Clear Device Metrics Log Failed") @@ -222,11 +223,6 @@ struct DeviceMetricsLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index d24ad831..a82197ae 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -12,6 +12,7 @@ struct EnvironmentMetricsLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var updateCoreDataController: UpdateCoreDataController @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" @@ -166,7 +167,7 @@ struct EnvironmentMetricsLog: View { titleVisibility: .visible ) { Button("Delete all environment metrics?", role: .destructive) { - if clearTelemetry(destNum: node.num, metricsType: 1, context: context) { + if updateCoreDataController.clearTelemetry(destNum: node.num, metricsType: 1) { Logger.services.error("Clear Environment Metrics Log Failed") } } @@ -199,11 +200,6 @@ struct EnvironmentMetricsLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift index 247a4380..2bc6801e 100644 --- a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift +++ b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift @@ -10,6 +10,9 @@ struct DeleteNodeButton: View { var connectedNode: NodeInfoEntity var node: NodeInfoEntity + + @EnvironmentObject + var queryCoreDataController: QueryCoreDataController @State private var isPresentingAlert = false @@ -31,10 +34,7 @@ struct DeleteNodeButton: View { titleVisibility: .visible ) { Button("Delete Node", role: .destructive) { - guard let deleteNode = getNodeInfo( - id: node.num, - context: context - ) else { + guard let deleteNode = queryCoreDataController.getNodeInfo(id: node.num) else { Logger.data.error("Unable to find node info to delete node \(node.num)") return } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 8d4f79b4..ea869e52 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -14,6 +14,8 @@ import SwiftUI struct WaypointForm: View { @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController + @Environment(\.managedObjectContext) private var context @Environment(\.dismiss) private var dismiss @State var waypoint: WaypointEntity let distanceFormatter = MKDistanceFormatter() @@ -184,13 +186,14 @@ struct WaypointForm: View { Menu { Button("For me", action: { - bleManager.context!.delete(waypoint) + context.delete(waypoint) do { - try bleManager.context!.save() + try context.save() } catch { - bleManager.context!.rollback() + context.rollback() } - dismiss() }) + dismiss() + }) Button("For everyone", action: { var newWaypoint = Waypoint() newWaypoint.id = UInt32(waypoint.id) @@ -213,11 +216,11 @@ struct WaypointForm: View { newWaypoint.expire = UInt32(1) if bleManager.sendWaypoint(waypoint: newWaypoint) { - bleManager.context!.delete(waypoint) + context.delete(waypoint) do { - try bleManager.context!.save() + try context.save() } catch { - bleManager.context!.rollback() + context.rollback() } dismiss() } else { @@ -351,7 +354,7 @@ struct WaypointForm: View { } .onAppear { if waypoint.id > 0 { - let waypoint = getWaypoint(id: Int64(waypoint.id), context: bleManager.context!) + let waypoint = queryCoreDataController.getWaypoint(id: Int64(waypoint.id)) name = waypoint.name ?? "Dropped Pin" description = waypoint.longDescription ?? "" icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 70cdcb8d..c221358a 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -18,6 +18,7 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @State private var showingShutdownConfirm: Bool = false @State private var showingRebootConfirm: Bool = false @State private var dateFormatRelative: Bool = true @@ -34,10 +35,7 @@ struct NodeDetail: View { var body: some View { NavigationStack { List { - let connectedNode = getNodeInfo( - id: bleManager.connectedPeripheral?.num ?? -1, - context: context - ) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) Section("Hardware") { NodeInfoItem(node: node) @@ -369,11 +367,6 @@ struct NodeDetail: View { } } .listStyle(.insetGrouped) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 096261ad..1d92ad7e 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -253,9 +253,6 @@ struct NodeList: View { } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } Task { await searchNodeList() } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index cd79ef0d..cefb7680 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -238,12 +238,9 @@ struct NodeMap: View { name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear(perform: { + .onAppear { UIApplication.shared.isIdleTimerDisabled = true - if self.bleManager.context == nil { - self.bleManager.context = context - } - }) + } .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false }) diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index 9042f091..3f1b09af 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -13,6 +13,7 @@ struct PaxCounterLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var updateCoreDataController: UpdateCoreDataController @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @@ -175,7 +176,7 @@ struct PaxCounterLog: View { titleVisibility: .visible ) { Button("paxcounter.delete", role: .destructive) { - if clearPax(destNum: node.num, context: context) { + if updateCoreDataController.clearPax(destNum: node.num) { Logger.services.info("Cleared Pax Counter for \(node.num)") } else { Logger.services.error("Clear Pax Counter Log Failed") @@ -209,11 +210,6 @@ struct PaxCounterLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } .fileExporter( isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 1c7ef170..ff01c612 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -10,6 +10,7 @@ import OSLog struct PositionLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var updateCoreDataController: UpdateCoreDataController @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? var useGrid: Bool { @@ -130,7 +131,7 @@ struct PositionLog: View { titleVisibility: .visible ) { Button("Delete all positions?", role: .destructive) { - if clearPositions(destNum: node.num, context: context) { + if updateCoreDataController.clearPositions(destNum: node.num) { Logger.services.info("Successfully Cleared Position Log") } else { Logger.services.error("Clear Position Log Failed") @@ -179,10 +180,5 @@ struct PositionLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index cd44cd30..9556298a 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -145,10 +145,5 @@ struct TraceRouteLog: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 8716c1a3..26d2b0d9 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -8,6 +8,7 @@ import OSLog struct AppSettings: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var persistenceController: PersistenceController @ObservedObject var tileManager = OfflineTileManager.shared @State var totalDownloadedTileSize = "" @State private var isPresentingCoreDataResetConfirm = false @@ -57,7 +58,7 @@ struct AppSettings: View { Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)") } } - clearCoreDataDatabase(context: context, includeRoutes: true) + persistenceController.clearCoreDataDatabase(context: context, includeRoutes: true) context.refreshAllObjects() UserDefaults.standard.reset() } @@ -94,10 +95,5 @@ struct AppSettings: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 816c6eb1..bec86958 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -280,11 +280,6 @@ struct Channels: View { ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 8b4033db..3c27e17c 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -12,6 +12,7 @@ import SwiftUI struct BluetoothConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @State var hasChanges = false @@ -80,7 +81,7 @@ struct BluetoothConfig: View { SaveConfigButton(node: node, hasChanges: $hasChanges) { if let myNodeNum = bleManager.connectedPeripheral?.num, - let connectedNode = getNodeInfo(id: myNodeNum, context: context) { + let connectedNode = queryCoreDataController.getNodeInfo(id: myNodeNum) { var bc = Config.BluetoothConfig() bc.enabled = enabled bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin @@ -107,14 +108,11 @@ struct BluetoothConfig: View { } ) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setBluetoothValues() // Need to request a BluetoothConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil { Logger.mesh.info("empty bluetooth config") - let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: connectedPeripheral.num) if let connectedNode { _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 1a872269..77d7cc06 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -13,6 +13,9 @@ struct DeviceConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var persistenceController: PersistenceController + @EnvironmentObject var queryCoreDataController: QueryCoreDataController + @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -166,7 +169,7 @@ struct DeviceConfig: View { if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { bleManager.disconnectPeripheral() - clearCoreDataDatabase(context: context, includeRoutes: false) + persistenceController.clearCoreDataDatabase(context: context, includeRoutes: false) } } else { @@ -191,7 +194,8 @@ struct DeviceConfig: View { if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { bleManager.disconnectPeripheral() - clearCoreDataDatabase(context: context, includeRoutes: false) + // TODO: re-enable me +// clearCoreDataDatabase(context: context, includeRoutes: false) } } else { Logger.mesh.error("Factory Reset Failed") @@ -202,7 +206,7 @@ struct DeviceConfig: View { } HStack { SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { var dc = Config.DeviceConfig() dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() @@ -238,14 +242,11 @@ struct DeviceConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setDeviceValues() // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil { Logger.mesh.info("empty device config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if node != nil && connectedNode != nil && connectedNode?.user != nil { _ = bleManager.requestDeviceConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 867f616d..35dcd81c 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -13,6 +13,7 @@ struct DisplayConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -129,7 +130,7 @@ struct DisplayConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.displayConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { var dc = Config.DisplayConfig() dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() @@ -159,15 +160,12 @@ struct DisplayConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setDisplayValues() // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.displayConfig == nil { Logger.mesh.info("empty display config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0) if node != nil && connectedNode != nil { _ = bleManager.requestDisplayConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 210675b2..18e02172 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -26,6 +26,7 @@ struct LoRaConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack @FocusState var focusedField: Field? @@ -191,7 +192,7 @@ struct LoRaConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.loRaConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0) if connectedNode != nil { var lc = Config.LoRaConfig() lc.hopLimit = UInt32(hopLimit) @@ -226,14 +227,11 @@ struct LoRaConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setLoRaValues() // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil { Logger.mesh.info("empty lora config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestLoRaConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index 3d3d3586..36c3de4b 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -12,6 +12,7 @@ struct AmbientLightingConfig: View { @Environment(\.self) var environment @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -59,7 +60,7 @@ struct AmbientLightingConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.ambientLightingConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { var al = ModuleConfig.AmbientLightingConfig() al.ledState = ledState @@ -85,13 +86,10 @@ struct AmbientLightingConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setAmbientLightingConfigValue() // Need to request a Ambient Lighting Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 37e16f93..ab171192 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -11,6 +11,7 @@ import SwiftUI struct CannedMessagesConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @@ -178,7 +179,7 @@ struct CannedMessagesConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.cannedMessageConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if hasChanges { if connectedNode != nil { var cmc = ModuleConfig.CannedMessageConfig() @@ -229,14 +230,11 @@ struct CannedMessagesConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setCannedMessagesValues() // Need to request a CannedMessagesModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil { Logger.mesh.info("empty canned messages module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 2e8f715b..46d0257f 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -26,6 +26,7 @@ struct DetectionSensorConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @@ -159,7 +160,7 @@ struct DetectionSensorConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if connectedNode != nil { var dsc = ModuleConfig.DetectionSensorConfig() dsc.enabled = self.enabled @@ -185,14 +186,11 @@ struct DetectionSensorConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setDetectionSensorValues() // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { Logger.mesh.info("empty detection sensor module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index a2adc9b6..3469529a 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -12,6 +12,7 @@ struct ExternalNotificationConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -162,7 +163,7 @@ struct ExternalNotificationConfig: View { } SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if connectedNode != nil { var enc = ModuleConfig.ExternalNotificationConfig() enc.enabled = enabled @@ -195,14 +196,11 @@ struct ExternalNotificationConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setExternalNotificationValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil { Logger.mesh.info("empty external notification module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 7eb5a945..b28be556 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -13,8 +13,11 @@ struct MQTTConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack + var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges: Bool = false @State var enabled = false @@ -246,7 +249,7 @@ struct MQTTConfig: View { } SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if connectedNode != nil { var mqtt = ModuleConfig.MQTTConfig() mqtt.enabled = self.enabled @@ -357,14 +360,11 @@ struct MQTTConfig: View { } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setMqttValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { Logger.mesh.info("empty mqtt module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 90a0e195a..404a7bae 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -11,6 +11,7 @@ import SwiftUI struct PaxCounterConfig: View { @Environment(\.managedObjectContext) private var context @EnvironmentObject private var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack let node: NodeInfoEntity? @@ -58,14 +59,11 @@ struct PaxCounterConfig: View { ) }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setPaxValues() // Need to request a PAX Counter module config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0) if node != nil && connectedNode != nil { _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } @@ -83,7 +81,7 @@ struct PaxCounterConfig: View { } SaveConfigButton(node: node, hasChanges: $hasChanges) { - guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context), + guard let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num), let fromUser = connectedNode.user, let toUser = node?.user else { return diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 2af3b5fd..888af520 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -12,6 +12,7 @@ struct RangeTestConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -56,7 +57,7 @@ struct RangeTestConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { var rtc = ModuleConfig.RangeTestConfig() rtc.enabled = enabled @@ -77,14 +78,11 @@ struct RangeTestConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setRangeTestValues() // Need to request a RangeTestModule Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil { Logger.mesh.debug("empty range test module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 6df3e504..22093c24 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -10,6 +10,7 @@ import SwiftUI struct RtttlConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -50,7 +51,7 @@ struct RtttlConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.rtttlConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { let adminMessageId = bleManager.saveRtttlConfig(ringtone: ringtone.trimmingCharacters(in: .whitespacesAndNewlines), fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { @@ -67,13 +68,10 @@ struct RtttlConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setRtttLConfigValue() // Need to request a Rtttl Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestRtttlConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index c4f27c6d..bbd2091f 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -12,6 +12,7 @@ struct SerialConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -104,7 +105,7 @@ struct SerialConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.serialConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { var sc = ModuleConfig.SerialConfig() sc.enabled = enabled @@ -133,14 +134,11 @@ struct SerialConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setSerialValues() // Need to request a SerialModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.serialConfig == nil { Logger.mesh.debug("empty serial module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index d5aba9cb..a58b1fb5 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -12,6 +12,7 @@ struct StoreForwardConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @@ -108,7 +109,7 @@ struct StoreForwardConfig: View { } SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if connectedNode != nil { /// Let the user set isRouter for the connected node, for nodes on the mesh set isRouter based /// on receipt of a primary heartbeat @@ -142,14 +143,10 @@ struct StoreForwardConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil { Logger.mesh.debug("empty store and forward module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 05b631b1..c16de9e0 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -12,6 +12,7 @@ struct TelemetryConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -104,7 +105,7 @@ struct TelemetryConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.telemetryConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if connectedNode != nil { var tc = ModuleConfig.TelemetryConfig() tc.deviceUpdateInterval = UInt32(deviceUpdateInterval) @@ -130,14 +131,11 @@ struct TelemetryConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setTelemetryValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil { Logger.mesh.info("empty telemetry module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 0dedaeed..ac9e5ab8 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -12,6 +12,7 @@ struct NetworkConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -89,7 +90,7 @@ struct NetworkConfig: View { .disabled(self.bleManager.connectedPeripheral == nil || node?.networkConfig == nil) SaveConfigButton(node: node, hasChanges: $hasChanges) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if connectedNode != nil { var network = Config.NetworkConfig() network.wifiEnabled = self.wifiEnabled @@ -114,14 +115,11 @@ struct NetworkConfig: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setNetworkValues() // Need to request a NetworkConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.networkConfig == nil { Logger.mesh.info("empty network config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num) if node != nil && connectedNode != nil { _ = bleManager.requestNetworkConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index a0d4ac97..c4df3bda 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -27,6 +27,7 @@ struct PositionConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @State var hasChanges = false @@ -290,7 +291,7 @@ struct PositionConfig: View { if fixedPosition && !supportedVersion { _ = bleManager.sendPosition(channel: 0, destNum: node?.num ?? 0, wantResponse: true) } - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral!.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral!.num) if connectedNode != nil { var pc = Config.PositionConfig() @@ -377,15 +378,12 @@ struct PositionConfig: View { } ) .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } setPositionValues() supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame // Need to request a PositionConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, node?.positionConfig == nil { Logger.mesh.info("empty position config") - let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: connectedPeripheral.num) if let node, let connectedNode { _ = bleManager.requestPositionConfig( fromUser: connectedNode.user!, diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 15b7e44d..ea1783d9 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -4,6 +4,7 @@ import MeshtasticProtobufs struct PowerConfig: View { @Environment(\.managedObjectContext) private var context @EnvironmentObject private var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack let node: NodeInfoEntity? @@ -118,10 +119,6 @@ struct PowerConfig: View { } } .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - Api().loadDeviceHardwareData { (hw) in for device in hw { @@ -136,7 +133,7 @@ struct PowerConfig: View { // Need to request a Power config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.powerConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0) if node != nil && connectedNode != nil { _ = bleManager.requestPowerConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } @@ -180,7 +177,7 @@ struct PowerConfig: View { } SaveConfigButton(node: node, hasChanges: $hasChanges) { - guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context), + guard let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num), let fromUser = connectedNode.user, let toUser = node?.user else { return diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 87f2bb88..d3cc2178 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -12,6 +12,7 @@ import OSLog struct Firmware: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController var node: NodeInfoEntity? @State var minimumVersion = "2.3.15" @State var version = "" @@ -108,7 +109,7 @@ struct Firmware: View { .foregroundStyle(.gray) .font(.caption) Button { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0) if connectedNode != nil { if bleManager.sendEnterDfuMode(fromUser: connectedNode!.user!, toUser: node!.user!) { @@ -158,7 +159,7 @@ struct Firmware: View { HStack(alignment: .center) { Spacer() Button { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0) if connectedNode != nil { if !bleManager.sendRebootOta(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { Logger.mesh.error("Reboot Failed") diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 487a3585..80bde09d 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -205,9 +205,9 @@ struct Settings: View { if selectedNode > 0 { let node = nodes.first(where: { $0.num == newValue }) let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) - preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + preferredNodeNum = Int(connectedNode?.num ?? 0) if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { - let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) + let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) if adminMessageId > 0 { Logger.mesh.info("Sent node metadata request from node details") } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 3859693e..136b9563 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -238,7 +238,6 @@ struct ShareChannels: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) .onAppear { - bleManager.context = context generateChannelSet() } .onChange(of: includeChannel0) { _ in generateChannelSet() } diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index c67c70bf..65c79424 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -12,6 +12,7 @@ struct UserConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @EnvironmentObject var queryCoreDataController: QueryCoreDataController @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @@ -149,8 +150,8 @@ struct UserConfig: View { return } - let connectedUser = getUser(id: bleManager.connectedPeripheral?.num ?? -1, context: context) - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + let connectedUser = queryCoreDataController.getUser(id: bleManager.connectedPeripheral?.num ?? -1) + let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1) if node != nil && connectedNode != nil { if !isLicensed {