diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 05c2bbb8..b447989f 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1429,8 +1429,15 @@ "Bad" : { }, - "Bad Packets: %d" : { - + "Bad Packets: %d %@%%" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Bad Packets: %1$d %2$@%%" + } + } + } }, "Bandwidth" : { @@ -16079,11 +16086,15 @@ "Override automatic OLED screen detection." : { }, - "Packets Received: %d" : { - - }, - "Packets Sent: %d" : { - + "Packets: Sent: %d Received: %d" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Packets: Sent: %1$d Received: %2$d" + } + } + } }, "password" : { "localizations" : { @@ -22314,6 +22325,9 @@ } } } + }, + "Uptime: %@" : { + }, "Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead." : { diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 3b6b2b65..d9801699 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -864,8 +864,10 @@ func textMessageAppPacket( newMessage.isEmoji = packet.decoded.emoji == 1 newMessage.channel = Int32(packet.channel) newMessage.portNum = Int32(packet.decoded.portnum.rawValue) - newMessage.publicKey = packet.publicKey - newMessage.pkiEncrypted = packet.pkiEncrypted + if newMessage.toUser?.pkiEncrypted ?? false { + newMessage.pkiEncrypted = true + newMessage.publicKey = packet.publicKey + } if packet.decoded.portnum == PortNum.detectionSensorApp { if !UserDefaults.enableDetectionNotifications { newMessage.read = true @@ -889,9 +891,11 @@ func textMessageAppPacket( newMessage.fromUser?.newPublicKey = newMessage.publicKey } } else { - /// We have no key, set it - newMessage.fromUser?.publicKey = packet.publicKey - newMessage.fromUser?.pkiEncrypted = packet.pkiEncrypted + /// We have no key, set it if it is not empty + if !packet.publicKey.isEmpty { + newMessage.fromUser?.pkiEncrypted = true + newMessage.fromUser?.publicKey = packet.publicKey + } } if packet.rxTime > 0 { newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 624dbb20..c1ee8470 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -181,8 +181,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newUser.role = Int32(newUserMessage.role.rawValue) newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue) - newUser.pkiEncrypted = packet.pkiEncrypted - newUser.publicKey = packet.publicKey + 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 }) @@ -209,8 +212,10 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } else { if packet.from > Constants.minimumNodeNum { let newUser = createUser(num: Int64(packet.from), context: context) - newNode.user?.pkiEncrypted = packet.pkiEncrypted - newNode.user?.publicKey = packet.publicKey + if !packet.publicKey.isEmpty { + newNode.user?.pkiEncrypted = true + newNode.user?.publicKey = packet.publicKey + } newNode.user = newUser } } @@ -224,6 +229,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) myInfoEntity.rebootCount = 0 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() @@ -271,10 +277,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) 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 !packet.publicKey.isEmpty { - fetchedNode[0].user!.pkiEncrypted = packet.pkiEncrypted - fetchedNode[0].user!.publicKey = packet.publicKey + if !nodeInfoMessage.user.publicKey.isEmpty { + fetchedNode[0].user!.pkiEncrypted = true + fetchedNode[0].user!.publicKey = nodeInfoMessage.user.publicKey } Task { Api().loadDeviceHardwareData { (hw) in diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 2a1eea84..3fdab7fd 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -92,6 +92,7 @@ struct Connect: View { } VStack { let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity + if localStats != nil { Divider() if localStats?.numTotalNodes ?? 0 >= 100 { @@ -111,25 +112,29 @@ struct Connect: View { .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) - Text("Packets Sent: \(localStats?.numPacketsTx ?? 0)") + Text("Packets: Sent: \(localStats?.numPacketsTx ?? 0) Received: \(localStats?.numPacketsRx ?? 0)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) - Text("Packets Received: \(localStats?.numPacketsRx ?? 0)") - .font(.caption) - .fontWeight(.medium) - .foregroundStyle(.secondary) - Text("Bad Packets: \(localStats?.numPacketsRxBad ?? 0)") + let errorRate = (Double(localStats?.numPacketsRxBad ?? -1) / Double(localStats?.numPacketsRx ?? -1)) * 100 + Text("Bad Packets: \(localStats?.numPacketsRxBad ?? 0) \(String(format: "Error Rate: %.2f", errorRate))%") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) .fixedSize() + let now = Date.now + let later = now + TimeInterval(Double(localStats?.numPacketsRxBad ?? 0)) + let uptime = (now..= 100 { + if context.state.totalNodes >= 100 { Text("100+ online") .font(.caption) .foregroundStyle(.secondary) @@ -81,7 +81,7 @@ struct WidgetsLiveActivity: Widget { .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Text("Bad \(context.state.badReceivedPackets)") + Text("Bad: \(context.state.badReceivedPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() @@ -97,7 +97,7 @@ struct WidgetsLiveActivity: Widget { } compactLeading: { Image("m-logo-black") .resizable() - .frame(width: 30.0) + .frame(width: 25) .padding(4) .background(.green.gradient, in: ContainerRelativeShape()) } compactTrailing: { @@ -120,26 +120,25 @@ struct WidgetsLiveActivity: Widget { } } -//struct WidgetsLiveActivity_Previews: PreviewProvider { -// static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G") -// static let state = MeshActivityAttributes.ContentState( -// timerRange: Date.now...Date(timeIntervalSinceNow: 60), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39, nodes: 17, nodesOnline: 9) -// -// static var previews: some View { -// attributes -// .previewContext(state, viewKind: .dynamicIsland(.compact)) -// .previewDisplayName("Compact") -// attributes -// .previewContext(state, viewKind: .dynamicIsland(.minimal)) -// .previewDisplayName("Minimal") -// attributes -// .previewContext(state, viewKind: .dynamicIsland(.expanded)) -// .previewDisplayName("Expanded") -// attributes -// .previewContext(state, viewKind: .content) -// .previewDisplayName("Notification") -// } -//} +struct WidgetsLiveActivity_Previews: PreviewProvider { + static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G") + static let state = MeshActivityAttributes.ContentState(uptimeSeconds: 600, channelUtilization: 1.2, airtime: 3.5, sentPackets: 12587, receivedPackets: 12555, badReceivedPackets: 800, nodesOnline: 99, totalNodes: 100, timerRange: Date.now...Date(timeIntervalSinceNow: 300)) + + static var previews: some View { + attributes + .previewContext(state, viewKind: .dynamicIsland(.compact)) + .previewDisplayName("Compact") + attributes + .previewContext(state, viewKind: .dynamicIsland(.minimal)) + .previewDisplayName("Minimal") + attributes + .previewContext(state, viewKind: .dynamicIsland(.expanded)) + .previewDisplayName("Expanded") + attributes + .previewContext(state, viewKind: .content) + .previewDisplayName("Notification") + } +} struct LiveActivityView: View { @Environment(\.colorScheme) private var colorScheme @@ -192,6 +191,7 @@ struct NodeInfoView: View { var timerRange: ClosedRange var body: some View { + let errorRate = (Double(badReceivedPackets) / Double(receivedPackets)) * 100 VStack(alignment: .leading, spacing: 0) { Text(nodeName) .font(nodeName.count > 14 ? .callout : .title3) @@ -203,19 +203,13 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("Packets Sent: \(sentPackets)") + Text("Packets: Sent \(sentPackets) Rec. \(receivedPackets)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("Packets Received: \(receivedPackets)") - .font(.caption) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.8 : 1.0) - .fixedSize() - Text("Bad Packets: \(badReceivedPackets)") + Text("Bad: \(badReceivedPackets) \(String(format: "Error Rate: %.2f", errorRate))%") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary)