// // 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, privacy: .public)") } } 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, privacy: .public)") } } 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, privacy: .public)") } } } func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, packet.from.toHex()) Logger.mesh.info("📟 \(logString, privacy: .public)") 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))) } else { newNode.firstHeard = Date() newNode.lastHeard = Date() } newNode.snr = packet.rxSnr newNode.rssi = packet.rxRssi newNode.viaMqtt = packet.viaMqtt if packet.to == Constants.maximumNodeNum || packet.to == UserDefaults.preferredPeripheralNum { newNode.channel = Int32(packet.channel) } if let nodeInfoMessage = try? NodeInfo(serializedBytes: packet.decoded.payload) { newNode.hopsAway = Int32(nodeInfoMessage.hopsAway) newNode.favorite = nodeInfoMessage.isFavorite } if let newUserMessage = try? User(serializedBytes: packet.decoded.payload) { if newUserMessage.id.isEmpty { if packet.from > Constants.minimumNodeNum { 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() newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue) if !newUserMessage.publicKey.isEmpty { newUser.pkiEncrypted = true newUser.publicKey = newUserMessage.publicKey } Task { Api().loadDeviceHardwareData { (hw) in let dh = hw.first(where: { $0.hwModel == newUser.hwModelId }) newUser.hwDisplayName = dh?.displayName } } newNode.user = newUser if UserDefaults.newNodeNotifications { let manager = LocalNotificationManager() manager.notifications = [ Notification( id: (UUID().uuidString), title: "New Node".localized, subtitle: "\(newUser.longName ?? "Unknown".localized)", content: "New Node has been discovered".localized, target: "nodes", path: "meshtastic:///nodes?nodenum=\(newUser.num)" ) ] manager.schedule() } } } else { if packet.from > Constants.minimumNodeNum { let newUser = createUser(num: Int64(packet.from), context: context) if !packet.publicKey.isEmpty { newNode.user?.pkiEncrypted = true newNode.user?.publicKey = packet.publicKey } newNode.user = newUser } } if newNode.user == nil && packet.from > Constants.minimumNodeNum { newNode.user = createUser(num: Int64(packet.from), context: context) } let myInfoEntity = MyInfoEntity(context: context) myInfoEntity.myNodeNum = Int64(packet.from) myInfoEntity.rebootCount = 0 newNode.myInfo = myInfoEntity do { try context.save() Logger.data.info("💾 [NodeInfo] Saved a NodeInfo for node number: \(packet.from.toHex(), privacy: .public)") 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)") } } 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))) } else { fetchedNode[0].lastHeard = Date() } fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi fetchedNode[0].viaMqtt = packet.viaMqtt if packet.to == Constants.maximumNodeNum || packet.to == UserDefaults.preferredPeripheralNum { fetchedNode[0].channel = Int32(packet.channel) } if let nodeInfoMessage = try? NodeInfo(serializedBytes: 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() fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) if !nodeInfoMessage.user.publicKey.isEmpty { fetchedNode[0].user!.pkiEncrypted = true fetchedNode[0].user!.publicKey = nodeInfoMessage.user.publicKey } Task { Api().loadDeviceHardwareData { (hw) in let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 }) fetchedNode[0].user!.hwDisplayName = dh?.displayName } } } } 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)) Logger.mesh.info("📍 \(logString, privacy: .public)") let fetchNodePositionRequest = NodeInfoEntity.fetchRequest() fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) do { if let positionMessage = try? Position(serializedBytes: 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))) } else { fetchedNode[0].lastHeard = Date() } 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 { 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum)) Logger.mesh.info("📶 \(logString, privacy: .public)") 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) 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum)) Logger.mesh.info("📟 \(logString, privacy: .public)") 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.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.tripleClickAsAdHocPing = !config.disableTripleClick 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?.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?.tripleClickAsAdHocPing = !config.disableTripleClick fetchedNode[0].deviceConfig?.ledHeartbeatEnabled = !config.ledHeartbeatDisabled fetchedNode[0].deviceConfig?.isManaged = config.isManaged fetchedNode[0].deviceConfig?.tzdef = config.tzdef } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex()) Logger.data.info("🖥️ \(logString, privacy: .public)") 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 } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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(), privacy: .public) 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex()) Logger.data.info("📻 \(logString, privacy: .public)") 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 newLoRaConfig.okToMqtt = config.configOkToMqtt 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?.okToMqtt = config.configOkToMqtt fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum)) Logger.data.info("🌐 \(logString, privacy: .public)") 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 newNetworkConfig.enabledProtocols = Int32(config.enabledProtocols) 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 fetchedNode[0].networkConfig?.enabledProtocols = Int32(config.enabledProtocols) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum)) Logger.data.info("🗺️ \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum)) Logger.data.info("🗺️ \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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 upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.security.config %@".localized, String(nodeNum)) Logger.data.info("🛡️ \(logString, privacy: .public)") let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) do { let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save Security Config if !fetchedNode.isEmpty { if fetchedNode[0].securityConfig == nil { let newSecurityConfig = SecurityConfigEntity(context: context) newSecurityConfig.publicKey = config.publicKey newSecurityConfig.privateKey = config.privateKey if config.adminKey.count > 0 { newSecurityConfig.adminKey = config.adminKey[0] } newSecurityConfig.isManaged = config.isManaged newSecurityConfig.serialEnabled = config.serialEnabled newSecurityConfig.debugLogApiEnabled = config.debugLogApiEnabled newSecurityConfig.adminChannelEnabled = config.adminChannelEnabled fetchedNode[0].securityConfig = newSecurityConfig } else { fetchedNode[0].securityConfig?.publicKey = config.publicKey fetchedNode[0].securityConfig?.privateKey = config.privateKey if config.adminKey.count > 0 { fetchedNode[0].securityConfig?.adminKey = config.adminKey[0] if config.adminKey.count > 1 { fetchedNode[0].securityConfig?.adminKey2 = config.adminKey[1] fetchedNode[0].securityConfig?.adminKey3 = config.adminKey[2] } } fetchedNode[0].securityConfig?.isManaged = config.isManaged fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled fetchedNode[0].securityConfig?.debugLogApiEnabled = config.debugLogApiEnabled fetchedNode[0].securityConfig?.adminChannelEnabled = config.adminChannelEnabled } if sessionPasskey?.count != 0 { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } do { try context.save() Logger.data.info("💾 [SecurityConfigEntity] Updated Security Config for node: \(nodeNum.toHex(), privacy: .public)") } catch { context.rollback() let nsError = error as NSError Logger.data.error("💥 [SecurityConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") } } else { Logger.data.error("💥 [SecurityConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Security Config") } } catch { let nsError = error as NSError Logger.data.error("💥 [SecurityConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") } } func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum)) Logger.data.info("🏮 \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum)) Logger.data.info("🥫 \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) Logger.data.info("🕵️ \(logString, privacy: .public)") 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.triggerType = Int32(config.detectionTriggerType.rawValue) 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?.triggerType = Int32(config.detectionTriggerType.rawValue) fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum)) Logger.data.info("📣 \(logString, privacy: .public)") 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 } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("PAX Counter config received: %@".localized, String(nodeNum)) Logger.data.info("🧑‍🤝‍🧑 \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum)) Logger.data.info("⛰️ \(logString, privacy: .public)") 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 } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum)) Logger.data.info("🌉 \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum)) Logger.data.info("⛰️ \(logString, privacy: .public)") 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 } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum)) Logger.data.info("🤖 \(logString, privacy: .public)") 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum)) Logger.data.info("📬 \(logString, privacy: .public)") 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) newConfig.isRouter = config.isServer 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) } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum)) Logger.data.info("📈 \(logString, privacy: .public)") 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 } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) } 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)") } }