diff --git a/Meshtastic/Accessory/Accessory Manager/AccessoryManager.swift b/Meshtastic/Accessory/Accessory Manager/AccessoryManager.swift index 5e2e98c8..8cc356e1 100644 --- a/Meshtastic/Accessory/Accessory Manager/AccessoryManager.swift +++ b/Meshtastic/Accessory/Accessory Manager/AccessoryManager.swift @@ -493,6 +493,14 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate { handleMyInfo(myNodeInfo) case .packet(let packet): + // All received packets get passed through updateAnyPacketFrom to update lastHeard, rxSnr, etc. (like firmware's NodeDB::updateFrom). + if let connectedNodeNum = self.activeDeviceNum { + updateAnyPacketFrom(packet: packet, activeDeviceNum: connectedNodeNum, context: context) + } else { + Logger.mesh.error("πŸ•ΈοΈ Unable to determine connectedNodeNum for updateAnyPacketFrom. Skipping.") + } + + // Dispatch based on packet contents. if case let .decoded(data) = packet.payloadVariant { switch data.portnum { case .textMessageApp, .detectionSensorApp, .alertApp: diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 380193a8..c56644a5 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -165,6 +165,60 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes } } +func updateAnyPacketFrom (packet: MeshPacket, activeDeviceNum: Int64, context: NSManagedObjectContext) { + // Update NodeInfoEntity for any packet received. This mirrors the firmware's NodeDB::updateFrom, which sniffs ALL received packets and updates the radio's nodeDB with packet.from's: + // - last_heard (from rxTime) + // - snr + // - via_mqtt + // - hops_away + + // However, unlike the firmware, this function will NOT create a new NodeInfoEntity if we don't have it already. We'll leave that to the existing code paths. + + // We do NOT update fetchedNode[0].channel, because we may hear a node over multiple channels, and only some packet types should update what we consider the node's channel to be. (Example: primary private channel, secondary public channel. A text message on the secondary public channel should NOT change fetchedNode[0].channel.) + + guard packet.from > 0 else { return } + guard packet.from != activeDeviceNum else { return } // Ignore if packet is from our own node + + let fetchNodeInfoAppRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoAppRequest) + if fetchedNode.count >= 1 { + 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))) + Logger.data.info("πŸ’Ύ [updateAnyPacketFrom] Updating node \(packet.from.toHex(), privacy: .public) lastHeard from rxTime=\(packet.rxTime)") + } else { + fetchedNode[0].lastHeard = Date() + Logger.data.info("πŸ’Ύ [updateAnyPacketFrom] Updating node \(packet.from.toHex(), privacy: .public) lastHeard to now (rxTime==0)") + } + + fetchedNode[0].snr = packet.rxSnr + fetchedNode[0].rssi = packet.rxRssi + fetchedNode[0].viaMqtt = packet.viaMqtt + + if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart { + fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) + Logger.data.info("πŸ’Ύ [updateAnyPacketFrom] Updating node \(packet.from.toHex(), privacy: .public) hopsAway=\(fetchedNode[0].hopsAway)") + } + + do { + try context.save() + Logger.data.info("πŸ’Ύ [updateAnyPacketFrom] Updating node \(fetchedNode[0].num.toHex(), privacy: .public) snr=\(fetchedNode[0].snr), rssi=\(fetchedNode[0].rssi) from packet \(packet.id.toHex(), privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("πŸ’₯ [updateAnyPacketFrom] Error Saving node \(fetchedNode[0].num.toHex(), privacy: .public) from packet \(packet.id.toHex(), privacy: .public) \(nsError, privacy: .public)") + } + } + } catch { + Logger.data.error("πŸ’₯ [updateAnyPacketFrom] fetch data error") + } +} + func upsertNodeInfoPacket (packet: MeshPacket, favorite: Bool = false, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("[NodeInfo] received for: %@".localized, packet.from.toHex()) @@ -316,16 +370,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, favorite: Bool = false, context: } 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) } @@ -463,22 +507,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) mutablePositions.removeAllObjects() } mutablePositions.add(position) - fetchedNode[0].id = Int64(packet.from) - fetchedNode[0].num = Int64(packet.from) - // Update the node's lastHeard. - // Some misconfigured nodes will broadcast position packets that claim GPS timestamps in the future. When updating lastHeard, don't use any future timestamps: fallback to using rxTime or Date() instead. - if positionMessage.time > 0 && (Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) <= Date()) { - 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