diff --git a/Meshtastic/Enums/IntervalEnums.swift b/Meshtastic/Enums/IntervalEnums.swift index 06087893..8a36933a 100644 --- a/Meshtastic/Enums/IntervalEnums.swift +++ b/Meshtastic/Enums/IntervalEnums.swift @@ -92,6 +92,7 @@ enum UpdateIntervals: Int, CaseIterable, Identifiable { case fifteenSeconds = 15 case thirtySeconds = 30 case oneMinute = 60 + case twoMinutes = 120 case fiveMinutes = 300 case tenMinutes = 600 case fifteenMinutes = 900 @@ -121,6 +122,8 @@ enum UpdateIntervals: Int, CaseIterable, Identifiable { return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: return NSLocalizedString("interval.one.minute", comment: "One Minute") + case .twoMinutes: + return NSLocalizedString("interval.two.minutes", comment: "Two Minutes") case .fiveMinutes: return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") case .tenMinutes: diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 2775e0ab..26124884 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -13,13 +13,13 @@ import ActivityKit #endif func generateMessageMarkdown (message: String) -> String { - + let types: NSTextCheckingResult.CheckingType = [.address, .link, .phoneNumber] let detector = try! NSDataDetector(types: types.rawValue) 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 { @@ -40,7 +40,7 @@ func generateMessageMarkdown (message: String) -> String { } 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) @@ -58,7 +58,7 @@ func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int6 } func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { - + if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(config.cannedMessage) { upsertCannedMessagesModuleConfigPacket(config: config.cannedMessage, nodeNum: nodeNum, context: context) } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(config.externalNotification) { @@ -75,20 +75,20 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu } func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedObjectContext) -> MyInfoEntity? { - + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.myinfo %@", comment: "MyInfo received: %@"), String(myInfo.myNodeNum)) MeshLogger.log("ℹ️ \(logString)") - + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) - + do { guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { return nil } // Not Found Insert if fetchedMyInfo.isEmpty { - + let myInfoEntity = MyInfoEntity(context: context) myInfoEntity.peripheralId = peripheralId myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) @@ -113,7 +113,7 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO print("πŸ’₯ Error Inserting New Core Data MyInfoEntity: \(nsError)") } } else { - + fetchedMyInfo[0].peripheralId = peripheralId fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum) fetchedMyInfo[0].hasGps = myInfo.hasGps_p @@ -125,7 +125,7 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec) fetchedMyInfo[0].minAppVersion = Int32(bitPattern: myInfo.minAppVersion) fetchedMyInfo[0].maxChannels = Int32(bitPattern: myInfo.maxChannels) - + do { try context.save() print("πŸ’Ύ Updated myInfo for node number: \(String(myInfo.myNodeNum))") @@ -143,15 +143,15 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO } func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectContext) { - + if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled { - + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.received %d %@", comment: "Channel %d received from: %@"), channel.index, String(fromNum)) MeshLogger.log("πŸŽ›οΈ \(logString)") - + let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum) - + do { guard let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as? [MyInfoEntity] else { return @@ -195,14 +195,14 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo } func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NSManagedObjectContext) { - + if metadata.isInitialized { let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.device.metadata.received %@", comment: "Device Metadata admin message received from: %@"), String(fromNum)) MeshLogger.log("🏷️ \(logString)") - + let fetchedNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchedNodeRequest.predicate = NSPredicate(format: "num == %lld", fromNum) - + do { guard let fetchedNode = try context.fetch(fetchedNodeRequest) as? [NodeInfoEntity] else { return @@ -218,7 +218,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS newMetadata.role = Int32(metadata.role.rawValue) newMetadata.positionFlags = Int32(metadata.positionFlags) fetchedNode[0].metadata = newMetadata - + do { try context.save() } catch { @@ -235,27 +235,27 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS } func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? { - + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(nodeInfo.num)) MeshLogger.log("πŸ“Ÿ \(logString)") - + guard nodeInfo.num > 0 else { return nil } - + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) - + do { guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { return nil } // Not Found Insert if fetchedNode.isEmpty && nodeInfo.hasUser { - + let newNode = NodeInfoEntity(context: context) newNode.id = Int64(nodeInfo.num) newNode.num = Int64(nodeInfo.num) newNode.channel = Int32(channel) - + if nodeInfo.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) @@ -266,7 +266,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newTelemetries.append(telemetry) newNode.telemetries? = NSOrderedSet(array: newTelemetries) } - + newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) newNode.snr = nodeInfo.snr if nodeInfo.hasUser { @@ -279,7 +279,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() 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.seqNo = Int32(nodeInfo.position.seqNumber) @@ -294,11 +294,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newPostions.append(position) newNode.positions? = NSOrderedSet(array: newPostions) } - + // Look for a MyInfo let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) - + do { guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { return nil @@ -318,15 +318,15 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje print("πŸ’₯ Fetch MyInfo Error") } } else if nodeInfo.hasUser && 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(channel) - + if nodeInfo.hasUser { - + fetchedNode[0].user!.userId = nodeInfo.user.id fetchedNode[0].user!.num = Int64(nodeInfo.num) fetchedNode[0].user!.longName = nodeInfo.user.longName @@ -334,9 +334,9 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.macaddr = nodeInfo.user.macaddr fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() } - + if nodeInfo.hasDeviceMetrics { - + let newTelemetry = TelemetryEntity(context: context) newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) newTelemetry.voltage = nodeInfo.deviceMetrics.voltage @@ -347,11 +347,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } 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.latitudeI = nodeInfo.position.latitudeI position.longitudeI = nodeInfo.position.longitudeI @@ -363,13 +363,13 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet } - + } - + // Look for a MyInfo let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) - + do { guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { return nil @@ -397,26 +397,57 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(packet.from)) MeshLogger.log("πŸ“Ÿ \(logString)") - + guard packet.from > 0 else { return } - + let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - + do { - + let fetchedNode = try context.fetch(fetchNodeInfoAppRequest) as? [NodeInfoEntity] ?? [] - - if fetchedNode.count == 1 { + + // Not Found Insert + if fetchedNode.count == 0 { + + let newNode = NodeInfoEntity(context: context) + newNode.id = Int64(packet.from) + newNode.num = Int64(packet.from) + newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + newNode.snr = packet.rxSnr + newNode.channel = Int32(packet.channel) + if let newUserMessage = try? User(serializedData: packet.decoded.payload) { + + let newUser = UserEntity(context: context) + newUser.userId = newUserMessage.id + newUser.num = Int64(packet.from) + newUser.longName = newUserMessage.longName + newUser.shortName = newUserMessage.shortName + newUser.macaddr = newUserMessage.macaddr + newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() + newNode.user = newUser + } + + if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { + let telemetry = TelemetryEntity(context: context) + telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel) + telemetry.voltage = telemetryMessage.deviceMetrics.voltage + telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization + telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx + var newTelemetries = [TelemetryEntity]() + newTelemetries.append(telemetry) + newNode.telemetries? = NSOrderedSet(array: newTelemetries) + } + } else { fetchedNode[0].id = Int64(packet.from) fetchedNode[0].num = Int64(packet.from) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) fetchedNode[0].snr = packet.rxSnr fetchedNode[0].channel = Int32(packet.channel) - + if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { if nodeInfoMessage.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) @@ -445,8 +476,6 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { let nsError = error as NSError print("πŸ’₯ Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)") } - } else { - // New node info not from device but potentially from another network } } catch { print("πŸ’₯ Error Fetching NodeInfoEntity for NODEINFO_APP") @@ -454,21 +483,21 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } 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(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from)) MeshLogger.log("πŸ₯« \(logString)") - + let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - + do { guard let fetchedNode = try context.fetch(fetchNodeRequest) as? [NodeInfoEntity] else { return @@ -495,65 +524,65 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } } 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.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 adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) { - + let moduleConfig = adminMessage.getModuleConfigResponse - + 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.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.telemetry(moduleConfig.telemetry) { upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context) - + } - + } else { MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Admin App \(try! packet.decoded.jsonString())") } - + // 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: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") fetchedAdminMessageRequest.predicate = NSPredicate(format: "messageId == %lld", packet.decoded.requestID) do { @@ -581,22 +610,22 @@ func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) { } 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 ?? NSLocalizedString("unknown", comment: "") let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.routing.message %@ %@", comment: "Routing received for RequestID: %@ Ack Status: %@"), String(packet.decoded.requestID), routingErrorString) MeshLogger.log("πŸ•ΈοΈ \(logString)") - + let fetchMessageRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID)) - + do { let fetchedMessage = try context.fetch(fetchMessageRequest) as? [MessageEntity] if fetchedMessage?.count ?? 0 > 0 { - + if fetchedMessage![0].toUser != nil { // Real ACK from DM Recipient if packet.to != packet.from { @@ -604,14 +633,14 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } } 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(packet.rxTime) - + if fetchedMessage![0].toUser != nil { fetchedMessage![0].toUser?.objectWillChange.send() } else { @@ -620,19 +649,19 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana do { let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] if fetchedMyInfo?.count ?? 0 > 0 { - + for ch in fetchedMyInfo![0].channels!.array as! [ChannelEntity] { - + if ch.index == packet.channel { ch.objectWillChange.send() } } } } catch { - + } } - + } else { return } @@ -647,25 +676,25 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } 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(NSLocalizedString("mesh.log.telemetry.received %@", comment: "Telemetry received for: %@"), String(packet.from)) MeshLogger.log("πŸ“ˆ \(logString)") } else { // If it is the connected node - + } - + let telemetry = TelemetryEntity(context: context) - + let fetchNodeTelemetryRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - + do { - + guard let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) as? [NodeInfoEntity] else { return } @@ -722,15 +751,15 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } } // Update our live activity if there is one running, not available on mac iOS >= 16.2 - #if !targetEnvironment(macCatalyst) +#if !targetEnvironment(macCatalyst) if #available(iOS 16.2, *) { - + 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)) 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 { @@ -740,7 +769,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } } } - #endif +#endif } } catch { context.rollback() @@ -753,11 +782,11 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) { - + if let messageText = String(bytes: packet.decoded.payload, encoding: .utf8) { - + MeshLogger.log("πŸ’¬ \(NSLocalizedString("mesh.log.textmessage.received", comment: "Message received from the text message app"))") - + let messageUsers: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from]) do { @@ -771,11 +800,11 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM newMessage.snr = packet.rxSnr newMessage.isEmoji = packet.decoded.emoji == 1 newMessage.channel = Int32(packet.channel) - + if packet.decoded.replyID > 0 { newMessage.replyID = Int64(packet.decoded.replyID) } - + if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != 4294967295 { newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to }) } @@ -784,20 +813,20 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM } newMessage.messagePayload = messageText newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText) - + newMessage.fromUser?.objectWillChange.send() newMessage.toUser?.objectWillChange.send() - + var messageSaved = false - + do { - + try context.save() print("πŸ’Ύ Saved a new message for \(newMessage.messageId)") messageSaved = true - + if messageSaved { - + if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) { // Create an iOS Notification for the received DM message and schedule it immediately let manager = LocalNotificationManager() @@ -811,10 +840,10 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM manager.schedule() print("πŸ’¬ iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") } else if newMessage.fromUser != nil && newMessage.toUser == nil { - + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) - + do { guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { return @@ -823,7 +852,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM if channel.index == newMessage.channel { context.refresh(channel, mergeChanges: true) } - + if channel.index == newMessage.channel && !channel.mute { // Create an iOS Notification for the received private channel message and schedule it immediately let manager = LocalNotificationManager() @@ -839,7 +868,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM } } } catch { - + } } } @@ -855,22 +884,22 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM } func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { - + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.received %@", comment: "Waypoint Packet received from node: %@"), String(packet.from)) MeshLogger.log("πŸ“ \(logString)") - + let fetchWaypointRequest: NSFetchRequest = NSFetchRequest.init(entityName: "WaypointEntity") fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id)) - + do { - + if let waypointMessage = try? Waypoint(serializedData: packet.decoded.payload) { guard let fetchedWaypoint = try context.fetch(fetchWaypointRequest) as? [WaypointEntity] else { return } if fetchedWaypoint.isEmpty { let waypoint = WaypointEntity(context: context) - + waypoint.id = Int64(packet.id) waypoint.name = waypointMessage.name waypoint.longDescription = waypointMessage.description_p diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift index c2756be5..83e04e5f 100644 --- a/Meshtastic/Views/Helpers/BatteryGauge.swift +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -15,7 +15,7 @@ struct BatteryGauge: View { var body: some View { VStack { - if batteryLevel == 0.0 { + if batteryLevel > 100.0 { // Plugged in Image(systemName: "powerplug") .font(.largeTitle) @@ -24,7 +24,7 @@ struct BatteryGauge: View { } else { let gradient = Gradient(colors: [.red, .orange, .green]) Gauge(value: batteryLevel, in: minValue...maxValue) { - if batteryLevel > 1.0 && batteryLevel < 10 { + if batteryLevel >= 0.0 && batteryLevel < 10 { Label("Battery Level %", systemImage: "battery.0") } else if batteryLevel >= 10.0 && batteryLevel < 25.00 { Label("Battery Level %", systemImage: "battery.25") @@ -38,7 +38,11 @@ struct BatteryGauge: View { Label("Battery Level %", systemImage: "battery.100.bolt") } } currentValueLabel: { - Text(Int(batteryLevel), format: .percent) + if batteryLevel == 0.0 { + Text("< 1%") + } else { + Text(Int(batteryLevel), format: .percent) + } } .tint(gradient) .gaugeStyle(.accessoryCircular) @@ -57,6 +61,7 @@ struct BatteryGauge_Previews: PreviewProvider { BatteryGauge(batteryLevel: 74.0) BatteryGauge(batteryLevel: 99.0) BatteryGauge(batteryLevel: 100.0) + BatteryGauge(batteryLevel: 111.0) } } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index a71cc4e8..89ea0c16 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -82,6 +82,7 @@ struct MapViewSwiftUI: UIViewRepresentable { #else #if os(iOS) + mapView.showsPointsOfInterest = false // Hide the default compass that only appears when you are not going north and instead always show the compass in the bottom right corner of the map mapView.showsCompass = false let compassButton = MKCompassButton(mapView: mapView) // Make a new compass @@ -199,7 +200,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.leftCalloutAccessoryView = leftIcon let subtitle = UILabel() subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n" - subtitle.text! += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n" + subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n" subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n" let distanceFormatter = MKDistanceFormatter() subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n" diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 59e42419..8505b0fd 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -57,7 +57,13 @@ struct NodeMap: View { ) @State private var overlays: [MapViewSwiftUI.Overlay] = [] +// init() { +// _positions = FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) +// } + var body: some View { + + NavigationStack { ZStack {