From 4ce4695061978f65fd3c05725885ca31dd9de5c8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 30 Mar 2023 11:21:59 -0700 Subject: [PATCH 01/23] Update RangeTestConfig.swift --- .../Views/Settings/Config/Module/RangeTestConfig.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 44d03ee9..c6fce6c5 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -67,18 +67,18 @@ struct RangeTestConfig: View { Label("save", systemImage: "square.and.arrow.down.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) + .disabled(!(node != nil && node?.metadata?.hasWifi ?? false)) Text("Saves a CSV with the range test message details, currently only available on ESP32 devices with a web server.") .font(.caption) } } - .disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil || !(node != nil && node!.myInfo?.hasWifi ?? false)) + .disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false)) Button { isPresentingSaveConfirm = true } label: { Label("save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node?.myInfo?.hasWifi ?? false)) + .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node?.metadata?.hasWifi ?? false)) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) From 4ddac10171ece72c6b6aec80a0908e9c5126572e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 30 Mar 2023 11:23:19 -0700 Subject: [PATCH 02/23] Update NetworkConfig.swift --- .../Views/Settings/Config/NetworkConfig.swift | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 21c48653..6386b9d7 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -1,5 +1,5 @@ // -// WiFiConfig.swift +// NetworkConfig.swift // Meshtastic // // Copyright (c) Garth Vander Houwen 8/1/2022 @@ -55,64 +55,68 @@ struct NetworkConfig: View { .font(.callout) .foregroundColor(.orange) } - Section(header: Text("WiFi Options (ESP32 Only)")) { - Toggle(isOn: $wifiEnabled) { - Label("enabled", systemImage: "wifi") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - HStack { - Label("ssid", systemImage: "network") - TextField("ssid", text: $wifiSsid) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: wifiSsid, perform: { _ in - let totalBytes = wifiSsid.utf8.count - // Only mess with the value if it is too big - if totalBytes > 32 { - let firstNBytes = Data(wifiSsid.utf8.prefix(32)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - wifiSsid = maxBytesString + if (node != nil && node?.metadata?.hasWifi ?? false) { + Section(header: Text("WiFi Options")) { + Toggle(isOn: $wifiEnabled) { + Label("enabled", systemImage: "wifi") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + HStack { + Label("ssid", systemImage: "network") + TextField("ssid", text: $wifiSsid) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: wifiSsid, perform: { _ in + let totalBytes = wifiSsid.utf8.count + // Only mess with the value if it is too big + if totalBytes > 32 { + let firstNBytes = Data(wifiSsid.utf8.prefix(32)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the shortName back to the last place where it was the right size + wifiSsid = maxBytesString + } } - } - hasChanges = true - }) - .foregroundColor(.gray) - } - .keyboardType(.default) - HStack { - Label("password", systemImage: "wallet.pass") - TextField("password", text: $wifiPsk) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: wifiPsk, perform: { _ in - let totalBytes = wifiPsk.utf8.count - // Only mess with the value if it is too big - if totalBytes > 63 { - let firstNBytes = Data(wifiPsk.utf8.prefix(63)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - wifiPsk = maxBytesString + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + HStack { + Label("password", systemImage: "wallet.pass") + TextField("password", text: $wifiPsk) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: wifiPsk, perform: { _ in + let totalBytes = wifiPsk.utf8.count + // Only mess with the value if it is too big + if totalBytes > 63 { + let firstNBytes = Data(wifiPsk.utf8.prefix(63)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the shortName back to the last place where it was the right size + wifiPsk = maxBytesString + } } - } - hasChanges = true - }) - .foregroundColor(.gray) + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + Text("Enabling WiFi will disable the bluetooth connection to the app.") + .font(.callout) } - .keyboardType(.default) - Text("Enabling WiFi will disable the bluetooth connection to the app.") - .font(.callout) + } - .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) - Section(header: Text("Ethernet Options")) { - Toggle(isOn: $ethEnabled) { - Label("enabled", systemImage: "network") + if (node != nil && node?.metadata?.hasEthernet ?? false) { + Section(header: Text("Ethernet Options")) { + Toggle(isOn: $ethEnabled) { + Label("enabled", systemImage: "network") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Enabling Ethernet will disable the bluetooth connection to the app.") + .font(.callout) } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("Enabling Ethernet will disable the bluetooth connection to the app.") - .font(.callout) } } .scrollDismissesKeyboard(.interactively) From 6aa2b3414a3f1954ae22a48552297e39bb0864bf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 30 Mar 2023 18:38:33 -0700 Subject: [PATCH 03/23] Update DeviceMetricsLog.swift --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 3ed3ad81..2a9b90d4 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -18,7 +18,10 @@ struct DeviceMetricsLog: View { var node: NodeInfoEntity var body: some View { + + NavigationStack { + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) let data = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) ?? [] if data.count > 0 { @@ -38,39 +41,32 @@ struct DeviceMetricsLog: View { } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] + Text("\(deviceMetrics.count) Readings") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + // Add a table for mac and ipad - Table(node.telemetries?.reversed() as? [TelemetryEntity] ?? []) { + //Table(Array(deviceMetrics),id: \.self) { + Table(deviceMetrics) { TableColumn("battery.level") { dm in - if dm.metricsType == 0 { - if dm.batteryLevel == 0 { - Text("Powered") - } else { - - Text("\(String(dm.batteryLevel))%") - } + if dm.batteryLevel > 100 { + Text("Powered") + } else { + Text("\(String(dm.batteryLevel))%") } } TableColumn("voltage") { dm in - if dm.metricsType == 0 { - Text("\(String(format: "%.2f", dm.voltage))") - } + Text("\(String(format: "%.2f", dm.voltage))") } TableColumn("channel.utilization") { dm in - if dm.metricsType == 0 { - Text(String(format: "%.2f", dm.channelUtilization)) - } + Text(String(format: "%.2f", dm.channelUtilization)) } TableColumn("airtime") { dm in - if dm.metricsType == 0 { - Text("\(String(format: "%.2f", dm.airUtilTx))%") - } + Text("\(String(format: "%.2f", dm.airUtilTx))%") } TableColumn("timestamp") { dm in - if dm.metricsType == 0 { - Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) - } + Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } } @@ -102,26 +98,25 @@ struct DeviceMetricsLog: View { .font(.caption) .fontWeight(.bold) } - ForEach(node.telemetries?.reversed() as? [TelemetryEntity] ?? [], id: \.self) { (dm: TelemetryEntity) in - if dm.metricsType == 0 { - GridRow { - if dm.batteryLevel == 111 { - Text("USB") - .font(.caption) - } else { - Text("\(String(dm.batteryLevel))%") - .font(.caption) - } - Text(String(dm.voltage)) - .font(.caption) - Text("\(String(format: "%.2f", dm.channelUtilization))%") - .font(.caption) - Text("\(String(format: "%.2f", dm.airUtilTx))%") - .font(.caption) - Text(dm.time?.formattedDate(format: dateFormatString) ?? "Unknown time") - .font(.caption2) + ForEach(deviceMetrics) { dm in + GridRow { + if dm.batteryLevel > 100 { + Text("USB") + .font(.caption) + } else { + Text("\(String(dm.batteryLevel))%") + .font(.caption) } + Text(String(dm.voltage)) + .font(.caption) + Text("\(String(format: "%.2f", dm.channelUtilization))%") + .font(.caption) + Text("\(String(format: "%.2f", dm.airUtilTx))%") + .font(.caption) + + Text(dm.time?.formattedDate(format: dateFormatString) ?? "Unknown time") + .font(.caption2) } } } From 64b3d42f2d99fb20a6cbb85c5709e58146017f24 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 30 Mar 2023 21:09:21 -0700 Subject: [PATCH 04/23] Update DeviceMetricsLog.swift --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 2a9b90d4..96a4e87e 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -19,14 +19,17 @@ struct DeviceMetricsLog: View { var body: some View { + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] + let batteryChartData = deviceMetrics + .filter { $0.time != nil && $0.time! >= oneDayAgo! } + .sorted { $0.time! < $1.time! } NavigationStack { - - let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) - let data = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) ?? [] - if data.count > 0 { + + if batteryChartData.count > 0 { GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) { - Chart(data.array as? [TelemetryEntity] ?? [], id: \.self) { + Chart(batteryChartData, id: \.self) { LineMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.batteryLevel) @@ -41,7 +44,7 @@ struct DeviceMetricsLog: View { } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") - let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] + //let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] Text("\(deviceMetrics.count) Readings") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { @@ -102,7 +105,7 @@ struct DeviceMetricsLog: View { ForEach(deviceMetrics) { dm in GridRow { if dm.batteryLevel > 100 { - Text("USB") + Text("PWD") .font(.caption) } else { Text("\(String(dm.batteryLevel))%") From 2e416020e0d26d8bfd86cc256ff817acb70e2b48 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 31 Mar 2023 12:08:42 -0700 Subject: [PATCH 05/23] Node Colors! --- Meshtastic/Helpers/Extensions.swift | 31 ++++++++++ Meshtastic/Helpers/MeshPackets.swift | 36 +++++------ Meshtastic/Views/Helpers/CircleText.swift | 3 +- .../Views/Map/Custom/MapViewSwiftUI.swift | 10 ++-- Meshtastic/Views/Messages/Contacts.swift | 2 +- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 60 ++++++++++++++++--- .../Views/Nodes/EnvironmentMetricsLog.swift | 58 +++++++----------- Meshtastic/Views/Nodes/NodeDetail.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 6 +- .../Config/Module/RangeTestConfig.swift | 2 +- 10 files changed, 136 insertions(+), 74 deletions(-) diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 9345d7ea..45705463 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -20,6 +20,22 @@ extension CLLocationCoordinate2D { } } +extension Color { + func isLight() -> Bool { + guard let components = cgColor?.components, components.count > 2 else {return false} + let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 + return (brightness > 0.5) + } +} + +extension UIColor { + func isLight() -> Bool { + guard let components = cgColor.components, components.count > 2 else {return false} + let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 + return (brightness > 0.5) + } +} + extension Data { var macAddressString: String { let mac: String = reduce("") {$0 + String(format: "%02x:", $1)} @@ -72,6 +88,21 @@ extension Int { } } + + +extension Int64 { + + func uiColor() -> UIColor { + let color = UIColor( + red: CGFloat((self & 0xFF0000) >> 16) / 255.0, + green: CGFloat((self & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(self & 0x0000FF) / 255.0, + alpha: CGFloat(1.0)) + + return color + } +} + extension UIImage { func rotate(radians: Float) -> UIImage? { var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 597d88d8..4579033e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -766,23 +766,25 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { return } - for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { - if channel.index == newMessage.channel { - context.refresh(channel, mergeChanges: true) - } - - if channel.index == newMessage.channel && !channel.mute { - // Create an iOS Notification for the received private channel message and schedule it immediately - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))", - subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", - content: messageText) - ] - manager.schedule() - print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") + if !fetchedMyInfo.isEmpty { + for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { + if channel.index == newMessage.channel { + context.refresh(channel, mergeChanges: true) + } + + if channel.index == newMessage.channel && !channel.mute { + // Create an iOS Notification for the received private channel message and schedule it immediately + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(newMessage.messageId)"), + title: "\(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))", + subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", + content: messageText) + ] + manager.schedule() + print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") + } } } } catch { diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index b4b51515..30d40406 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -11,6 +11,7 @@ struct CircleText: View { var circleSize: CGFloat? = 60 var fontSize: CGFloat? = 20 var brightness: Double? = 0 + var textColor: Color? = .white var body: some View { @@ -21,7 +22,7 @@ struct CircleText: View { .fill(color) .brightness(brightness ?? 0) .frame(width: circleSize, height: circleSize) - Text(text).textCase(.uppercase).font(font).foregroundColor(.white).fixedSize() + Text(text).textCase(.uppercase).font(font).foregroundColor(textColor).fixedSize() .frame(width: circleSize, height: circleSize, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).offset(x: 0, y: 0) } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 86f8c0a8..a32e606c 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -16,8 +16,6 @@ struct MapViewSwiftUI: UIViewRepresentable { var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void var onWaypointEdit: (_ waypointId: Int ) -> Void let mapView = MKMapView() - let lineColors: [UIColor] = [UIColor.systemIndigo, UIColor.yellow, UIColor.white, UIColor.red, UIColor.purple, UIColor.orange, UIColor.magenta, UIColor.lightGray, UIColor.green, UIColor.gray, UIColor.systemMint, UIColor.darkGray, UIColor.cyan, UIColor.brown, UIColor.blue, UIColor.black, UIColor.systemPink, - UIColor.systemTeal] // Parameters let positions: [PositionEntity] let waypoints: [WaypointEntity] @@ -142,7 +140,7 @@ struct MapViewSwiftUI: UIViewRepresentable { return position.nodeCoordinate! }) let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) - polyline.title = "\(String(position.nodePosition?.num ?? 0))-\(String(lineIndex))" + polyline.title = "\(String(position.nodePosition?.num ?? 0))" mapView.addOverlay(polyline) lineIndex += 1 // There are 18 colors for lines, start over if we are at index 17 @@ -199,7 +197,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.displayPriority = .required annotationView.titleVisibility = .visible } else { - annotationView.markerTintColor = UIColor(.indigo) + annotationView.markerTintColor = positionAnnotation.nodePosition?.num.uiColor() annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive } @@ -351,10 +349,10 @@ struct MapViewSwiftUI: UIViewRepresentable { } else { if let routePolyline = overlay as? MKPolyline { - let titleString = routePolyline.title ?? "None-0" + let titleString = routePolyline.title ?? "0" let index = Int(titleString.components(separatedBy: "-").last ?? "0") let renderer = MKPolylineRenderer(polyline: routePolyline) - renderer.strokeColor = parent.lineColors[index ?? 0] + renderer.strokeColor = Int64(titleString)?.uiColor() renderer.lineWidth = 8 return renderer } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 3a193e4f..a2e1dba2 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -150,7 +150,7 @@ struct Contacts: View { HStack { VStack { HStack { - CircleText(text: user.shortName ?? "???", color: .accentColor, circleSize: 52, fontSize: 16, brightness: 0.1) + CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 52, fontSize: 16, textColor: user.num.uiColor().isLight() ? .black : .white) .padding(.trailing, 5) VStack { HStack { diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 96a4e87e..9bf5bed7 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -21,25 +21,67 @@ struct DeviceMetricsLog: View { let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] - let batteryChartData = deviceMetrics + let chartData = deviceMetrics .filter { $0.time != nil && $0.time! >= oneDayAgo! } .sorted { $0.time! < $1.time! } NavigationStack { - if batteryChartData.count > 0 { + if chartData.count > 0 { + GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) { - Chart(batteryChartData, id: \.self) { - LineMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), - y: .value("Value", $0.batteryLevel) - ) + + Chart(chartData, id: \.self) { + PointMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), + x: .value("Time", $0.time!, unit: .hour), + y: .value("Value", $0.channelUtilization) + ) + .foregroundStyle(.green) + + LineMark( + x: .value("Time", $0.time!, unit: .hour), + y: .value("Value", $0.channelUtilization) + ) + .foregroundStyle(.green) + .interpolationMethod(.catmullRom) + + PointMark( + x: .value("Time", $0.time!, unit: .hour), y: .value("Value", $0.batteryLevel) ) + .foregroundStyle(.blue) + + LineMark( + x: .value("Time", $0.time!, unit: .hour), + y: .value("Value", $0.batteryLevel) + ) + .foregroundStyle(.blue) + + PointMark( + //x: .value("Hour", $0.time!.formattedDate(format: "ha")), + x: .value("Time", $0.time!, unit: .hour), + y: .value("Value", $0.airUtilTx) + ) + .foregroundStyle(.red) + + LineMark( + x: .value("Time", $0.time!, unit: .hour), + y: .value("Value", $0.airUtilTx) + ) + .foregroundStyle(.red) } - .frame(height: 150) + // Set color for each data in the chart + .chartForegroundStyleScale([ + "Battery Level" : .blue, + "Channel Utilization": .green, + "Airtime": .red + ]) + .chartLegend(position: .automatic, alignment: .bottom) + .chartXAxis { + AxisMarks(values: .stride(by: .hour)) + } + //.frame(height: 200) } } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 46a16dd8..e9655414 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -19,47 +19,36 @@ struct EnvironmentMetricsLog: View { var node: NodeInfoEntity var body: some View { + + let environmentMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).reversed() as? [TelemetryEntity] ?? [] NavigationStack { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") + Text("\(environmentMetrics.count) Readings") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { // Add a table for mac and ipad - Table(node.telemetries!.reversed() as! [TelemetryEntity]) { + Table(environmentMetrics) { TableColumn("Temperature") { em in - if em.metricsType == 1 { - Text(em.temperature.formattedTemperature()) - } + Text(em.temperature.formattedTemperature()) } TableColumn("Humidity") { em in - if em.metricsType == 1 { - Text("\(String(format: "%.2f", em.relativeHumidity))%") - } + Text("\(String(format: "%.2f", em.relativeHumidity))%") } TableColumn("Barometric Pressure") { em in - if em.metricsType == 1 { - Text("\(String(format: "%.2f", em.barometricPressure)) hPa") - } + Text("\(String(format: "%.2f", em.barometricPressure)) hPa") } TableColumn("gas.resistance") { em in - if em.metricsType == 1 { - Text("\(String(format: "%.2f", em.gasResistance)) ohms") - } + Text("\(String(format: "%.2f", em.gasResistance)) ohms") } TableColumn("current") { em in - if em.metricsType == 1 { - Text("\(String(format: "%.2f", em.current))") - } + Text("\(String(format: "%.2f", em.current))") } TableColumn("voltage") { em in - if em.metricsType == 1 { - Text("\(String(format: "%.2f", em.voltage))") - } + Text("\(String(format: "%.2f", em.voltage))") } TableColumn("timestamp") { em in - if em.metricsType == 1 { - Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) - } + Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } } } else { @@ -92,21 +81,18 @@ struct EnvironmentMetricsLog: View { } ForEach(node.telemetries?.reversed() as? [TelemetryEntity] ?? [], id: \.self) { (em: TelemetryEntity) in - if em.metricsType == 1 { + GridRow { - GridRow { - - Text(em.temperature.formattedTemperature()) - .font(.caption) - Text("\(String(format: "%.2f", em.relativeHumidity))%") - .font(.caption) - Text("\(String(format: "%.2f", em.barometricPressure))") - .font(.caption) - Text("\(String(format: "%.2f", em.gasResistance))") - .font(.caption) - Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) - .font(.caption2) - } + Text(em.temperature.formattedTemperature()) + .font(.caption) + Text("\(String(format: "%.2f", em.relativeHumidity))%") + .font(.caption) + Text("\(String(format: "%.2f", em.barometricPressure))") + .font(.caption) + Text("\(String(format: "%.2f", em.gasResistance))") + .font(.caption) + Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) + .font(.caption2) } } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 24634d6c..b80f4be1 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -145,7 +145,7 @@ struct NodeDetail: View { if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { HStack { VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) + CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 75, fontSize: 26, textColor: node.num.uiColor().isLight() ? .black : .white ) } Divider() VStack { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 983b9846..043140e9 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -26,9 +26,11 @@ struct NodeList: View { @State private var selection: NodeInfoEntity? // Nothing selected by default. var body: some View { + + NavigationSplitView { - List(nodes, id: \.self, selection: $selection) { node in + List(nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { Text("no.nodes").font(.title) } else { @@ -36,7 +38,7 @@ struct NodeList: View { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) VStack(alignment: .leading) { HStack { - CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 52, fontSize: 16, brightness: 0.1) + CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 52, fontSize: 16, brightness: 0.0, textColor: node.num.uiColor().isLight() ? .black : .white) .padding(.trailing, 5) VStack(alignment: .leading) { Text(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index c6fce6c5..45665dea 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -72,7 +72,7 @@ struct RangeTestConfig: View { .font(.caption) } } - .disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false)) + .disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false)) Button { isPresentingSaveConfirm = true } label: { From 0ba5846eda1ffb97268e2053c45e252638053bfe Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 31 Mar 2023 12:10:15 -0700 Subject: [PATCH 06/23] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 4e131874..985c4c5f 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1199,7 +1199,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.4; + MARKETING_VERSION = 2.1.6; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1233,7 +1233,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.4; + MARKETING_VERSION = 2.1.6; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; From 6dc7abc42c09e66f0ce7503450a6b40c5b4213b3 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 31 Mar 2023 18:49:30 -0700 Subject: [PATCH 07/23] Additional color cleanup --- Meshtastic/Helpers/Extensions.swift | 29 +++++++++++++++---- Meshtastic/Views/Messages/Contacts.swift | 4 +-- .../Views/Messages/UserMessageList.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 4 +-- en.lproj/Localizable.strings | 2 +- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 45705463..f744ca65 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -88,21 +88,38 @@ extension Int { } } - - extension Int64 { func uiColor() -> UIColor { let color = UIColor( - red: CGFloat((self & 0xFF0000) >> 16) / 255.0, - green: CGFloat((self & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(self & 0x0000FF) / 255.0, + red: CGFloat((UInt32(self) & 0xFF0000) >> 16) / 255.0, + green: CGFloat((UInt32(self) & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(UInt32(self) & 0x0000FF) / 255.0, alpha: CGFloat(1.0)) - + return color } } +//extension Int64 { +// +// func uiColor() -> UIColor { +// +// let bytes = withUnsafeBytes(of: UInt32(self).littleEndian, Array.init) +// let redBytes = bytes[0] +// let greenBytes = bytes[1] +// let blueBytes = bytes[2] +// let color = UIColor( +// +// red: CGFloat(redBytes), +// green: CGFloat(greenBytes), +// blue: CGFloat(blueBytes), +// alpha: CGFloat(1.0)) +// +// return color +// } +//} + extension UIImage { func rotate(radians: Float) -> UIImage? { var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index a2e1dba2..48d80658 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -45,7 +45,7 @@ struct Contacts: View { let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 VStack(alignment: .leading) { HStack { - CircleText(text: String(channel.index), color: .accentColor, circleSize: 52, fontSize: 40, brightness: 0.1) + CircleText(text: String(channel.index), color: .accentColor, circleSize: 60, fontSize: 42, brightness: 0.1) .padding(.trailing, 5) VStack { HStack { @@ -150,7 +150,7 @@ struct Contacts: View { HStack { VStack { HStack { - CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 52, fontSize: 16, textColor: user.num.uiColor().isLight() ? .black : .white) + CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 60, fontSize: 18, textColor: user.num.uiColor().isLight() ? .black : .white) .padding(.trailing, 5) VStack { HStack { diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index ee6ee822..8db5f23e 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -360,7 +360,7 @@ struct UserMessageList: View { .toolbar { ToolbarItem(placement: .principal) { HStack { - CircleText(text: user.shortName ?? "???", color: .accentColor, circleSize: 44, fontSize: 14).fixedSize() + CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 44, fontSize: 14, textColor: user.num.uiColor().isLight() ? .black : .white ).fixedSize() Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index b80f4be1..742d97d5 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -259,7 +259,7 @@ struct NodeDetail: View { HStack { VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: .accentColor) + CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), textColor: node.num.uiColor().isLight() ? .black : .white ) } Divider() VStack { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 043140e9..27e7664e 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -26,8 +26,6 @@ struct NodeList: View { @State private var selection: NodeInfoEntity? // Nothing selected by default. var body: some View { - - NavigationSplitView { List(nodes, id: \.self, selection: $selection) { node in @@ -38,7 +36,7 @@ struct NodeList: View { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) VStack(alignment: .leading) { HStack { - CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 52, fontSize: 16, brightness: 0.0, textColor: node.num.uiColor().isLight() ? .black : .white) + CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 65, fontSize: 20, brightness: 0.0, textColor: node.num.uiColor().isLight() ? .black : .white) .padding(.trailing, 5) VStack(alignment: .leading) { Text(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 8183f1cf..b0ad7edc 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -51,7 +51,7 @@ "config.save.confirm"="After config values save the node will reboot."; "communicating"="Communicating with device. ."; "connected.radio"="Connected Radio"; -"connected"="Currently Connected"; +"connected"="Connected"; "connecting"="Connecting . ."; "contacts"="Contacts"; "copy"="Copy"; From 6ed72b0a425b7e85a107c8fe2a8cabdf3ce83d88 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 01:37:26 -0700 Subject: [PATCH 08/23] Clean up number to UIColor conversion --- Meshtastic/Helpers/Extensions.swift | 47 ++++++------------- .../Views/Map/Custom/MapViewSwiftUI.swift | 4 +- Meshtastic/Views/Messages/Contacts.swift | 2 +- .../Views/Messages/UserMessageList.swift | 4 +- Meshtastic/Views/Nodes/NodeDetail.swift | 4 +- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 6 files changed, 22 insertions(+), 41 deletions(-) diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index f744ca65..716eeb24 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -12,7 +12,7 @@ extension Character { extension CLLocationCoordinate2D { /// Returns distance from coordianate in meters. /// - Parameter from: coordinate which will be used as end point. - /// - Returns: Returns distance in meters. + /// - Returns: distance in meters. func distance(from: CLLocationCoordinate2D) -> CLLocationDistance { let from = CLLocation(latitude: from.latitude, longitude: from.longitude) let to = CLLocation(latitude: self.latitude, longitude: self.longitude) @@ -21,6 +21,8 @@ extension CLLocationCoordinate2D { } extension Color { + /// Returns a boolean for a SwiftUI Color to determine what color of text to use + /// - Returns: true if the color is light func isLight() -> Bool { guard let components = cgColor?.components, components.count > 2 else {return false} let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 @@ -29,11 +31,22 @@ extension Color { } extension UIColor { + /// Returns a boolean for a UIColor to determine what color of text to use + /// - Returns: true if the color is light func isLight() -> Bool { guard let components = cgColor.components, components.count > 2 else {return false} let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 return (brightness > 0.5) } + /// Returns a UIColor from a UInt32 value + /// - Parameter hex: UInt32 value to convert to a color + /// - Returns: UIColor + convenience init(hex: UInt32) { + let red = CGFloat((hex & 0xFF0000) >> 16) + let green = CGFloat((hex & 0x00FF00) >> 8) + let blue = CGFloat((hex & 0x0000FF)) + self.init(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1.0) + } } extension Data { @@ -88,38 +101,6 @@ extension Int { } } -extension Int64 { - - func uiColor() -> UIColor { - let color = UIColor( - red: CGFloat((UInt32(self) & 0xFF0000) >> 16) / 255.0, - green: CGFloat((UInt32(self) & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(UInt32(self) & 0x0000FF) / 255.0, - alpha: CGFloat(1.0)) - - return color - } -} - -//extension Int64 { -// -// func uiColor() -> UIColor { -// -// let bytes = withUnsafeBytes(of: UInt32(self).littleEndian, Array.init) -// let redBytes = bytes[0] -// let greenBytes = bytes[1] -// let blueBytes = bytes[2] -// let color = UIColor( -// -// red: CGFloat(redBytes), -// green: CGFloat(greenBytes), -// blue: CGFloat(blueBytes), -// alpha: CGFloat(1.0)) -// -// return color -// } -//} - extension UIImage { func rotate(radians: Float) -> UIImage? { var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index a32e606c..269b913a 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -197,7 +197,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.displayPriority = .required annotationView.titleVisibility = .visible } else { - annotationView.markerTintColor = positionAnnotation.nodePosition?.num.uiColor() + annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)) annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive } @@ -352,7 +352,7 @@ struct MapViewSwiftUI: UIViewRepresentable { let titleString = routePolyline.title ?? "0" let index = Int(titleString.components(separatedBy: "-").last ?? "0") let renderer = MKPolylineRenderer(polyline: routePolyline) - renderer.strokeColor = Int64(titleString)?.uiColor() + renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0) renderer.lineWidth = 8 return renderer } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 48d80658..8292d080 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -150,7 +150,7 @@ struct Contacts: View { HStack { VStack { HStack { - CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 60, fontSize: 18, textColor: user.num.uiColor().isLight() ? .black : .white) + CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 60, fontSize: 18, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white) .padding(.trailing, 5) VStack { HStack { diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 8db5f23e..fa3dc19d 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -58,7 +58,7 @@ struct UserMessageList: View { HStack(alignment: .top) { if currentUser { Spacer(minLength: 50) } if !currentUser { - CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray), circleSize: 44, fontSize: 14) + CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray)) .padding(.all, 5) .offset(y: -5) } @@ -360,7 +360,7 @@ struct UserMessageList: View { .toolbar { ToolbarItem(placement: .principal) { HStack { - CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 44, fontSize: 14, textColor: user.num.uiColor().isLight() ? .black : .white ).fixedSize() + CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 44, fontSize: 14, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white ).fixedSize() Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 742d97d5..525f094e 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -145,7 +145,7 @@ struct NodeDetail: View { if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { HStack { VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 75, fontSize: 26, textColor: node.num.uiColor().isLight() ? .black : .white ) + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 75, fontSize: 24, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) } Divider() VStack { @@ -259,7 +259,7 @@ struct NodeDetail: View { HStack { VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), textColor: node.num.uiColor().isLight() ? .black : .white ) + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) } Divider() VStack { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 27e7664e..33e29fcd 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -36,7 +36,7 @@ struct NodeList: View { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) VStack(alignment: .leading) { HStack { - CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 65, fontSize: 20, brightness: 0.0, textColor: node.num.uiColor().isLight() ? .black : .white) + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: 20, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white) .padding(.trailing, 5) VStack(alignment: .leading) { Text(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) From e10c2e94b26b3f296f2a83017f2d9fa359c3d898 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 02:35:31 -0700 Subject: [PATCH 09/23] Add user colors to channel group chats --- Meshtastic/Views/Helpers/LastHeardText.swift | 2 +- Meshtastic/Views/Messages/ChannelMessageList.swift | 2 +- Meshtastic/Views/Messages/UserMessageList.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift index 1aaaa959..3a207b6d 100644 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ b/Meshtastic/Views/Helpers/LastHeardText.swift @@ -10,7 +10,7 @@ struct LastHeardText: View { let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) var body: some View { if lastHeard != nil && lastHeard! >= sixMonthsAgo! { - Text("heard")+Text(": \(lastHeard!, style: .relative) ")+Text("ago") + Text("heard")+Text(" \(lastHeard!, style: .relative) ")+Text("ago") } else { Text("unknown.age") } diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 3297f28f..3c44cb54 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -57,7 +57,7 @@ struct ChannelMessageList: View { HStack(alignment: .top) { if currentUser { Spacer(minLength: 50) } if !currentUser { - CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray), circleSize: 44, fontSize: 14) + CircleText(text: message.fromUser?.shortName ?? "????", color: Color(UIColor(hex: UInt32(message.fromUser?.num ?? 0))), circleSize: 44, fontSize: 14, textColor: UIColor(hex: UInt32(message.fromUser?.num ?? 0)).isLight() ? .black : .white) .padding(.all, 5) .offset(y: -5) } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index fa3dc19d..24952085 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -58,7 +58,7 @@ struct UserMessageList: View { HStack(alignment: .top) { if currentUser { Spacer(minLength: 50) } if !currentUser { - CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray)) + CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray), circleSize: 44, fontSize: 14) .padding(.all, 5) .offset(y: -5) } From 7abe9bfc8e90e9fde29977be326030a57e55d985 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 02:49:01 -0700 Subject: [PATCH 10/23] Remove circle from DM's --- Meshtastic/Views/Messages/UserMessageList.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 24952085..fa2661be 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -57,11 +57,6 @@ struct UserMessageList: View { } HStack(alignment: .top) { if currentUser { Spacer(minLength: 50) } - if !currentUser { - CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray), circleSize: 44, fontSize: 14) - .padding(.all, 5) - .offset(y: -5) - } VStack(alignment: currentUser ? .trailing : .leading) { let markdownText: LocalizedStringKey = LocalizedStringKey.init(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE")) From ff5b160db95f1d30d624d4ba084a752b1de2cb32 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 03:12:10 -0700 Subject: [PATCH 11/23] Chart updates on device metrics --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 56 +++++++++---------- de.lproj/Localizable.strings | 1 - en.lproj/Localizable.strings | 1 - zh-Hans.lproj/Localizable.strings | 1 - 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 9bf5bed7..05e53f70 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -19,7 +19,7 @@ struct DeviceMetricsLog: View { var body: some View { - let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) + let oneDayAgo = Calendar.current.date(byAdding: .hour, value: -6, to: Date()) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] let chartData = deviceMetrics .filter { $0.time != nil && $0.time! >= oneDayAgo! } @@ -29,47 +29,45 @@ struct DeviceMetricsLog: View { if chartData.count > 0 { - GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) { + GroupBox() {//label: Label("battery.level.trend", systemImage: "battery.100")) { Chart(chartData, id: \.self) { - - PointMark( - x: .value("Time", $0.time!, unit: .hour), - y: .value("Value", $0.channelUtilization) - ) - .foregroundStyle(.green) LineMark( - x: .value("Time", $0.time!, unit: .hour), - y: .value("Value", $0.channelUtilization) - ) - .foregroundStyle(.green) - .interpolationMethod(.catmullRom) - - PointMark( - x: .value("Time", $0.time!, unit: .hour), + x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.batteryLevel) ) .foregroundStyle(.blue) - - LineMark( - x: .value("Time", $0.time!, unit: .hour), + PointMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.batteryLevel) ) .foregroundStyle(.blue) +// LineMark( +// x: .value("Hour", $0.time!.formattedDate(format: "ha")), +// y: .value("Value", $0.channelUtilization) +// ) +// .foregroundStyle(.green) +// .interpolationMethod(.catmullRom) PointMark( - //x: .value("Hour", $0.time!.formattedDate(format: "ha")), - x: .value("Time", $0.time!, unit: .hour), + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.channelUtilization) + ) + .foregroundStyle(.green) + +// LineMark( +// x: .value("Time", $0.time!, unit: .hour), +// y: .value("Value", $0.airUtilTx) +// ) +// .foregroundStyle(.red) + PointMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + //x: .value("Time", $0.time!, unit: .hour), y: .value("Value", $0.airUtilTx) ) .foregroundStyle(.red) - LineMark( - x: .value("Time", $0.time!, unit: .hour), - y: .value("Value", $0.airUtilTx) - ) - .foregroundStyle(.red) } // Set color for each data in the chart .chartForegroundStyleScale([ @@ -78,9 +76,9 @@ struct DeviceMetricsLog: View { "Airtime": .red ]) .chartLegend(position: .automatic, alignment: .bottom) - .chartXAxis { - AxisMarks(values: .stride(by: .hour)) - } +// .chartXAxis { +// AxisMarks(values: .stride(by: .hour)) +// } //.frame(height: 200) } } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index d42c9caa..851303bc 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -18,7 +18,6 @@ "available.radios"="Geräte in der Nähe"; "automatic.detection"="Automatische erkennung"; "battery.level"="Batterie Ladung"; -"battery.level.trend"="Batterie Ladungstrend"; "ble.name"="BLE Name";"ble.connection.timeout %d %@"="Verbindung nach %d Versuchen zu %@ fehlgeschlagen. Evtl. hilft es, die Verbindung unter Einstellungen > Bluetooth manuell zu löschen."; "ble.connection.timeout %d %@"="Verbindung nach %d Versuchen zu %@ fehlgeschlagen. Evtl. hilft es, die Verbindung unter Einstellungen > Bluetooth manuell zu löschen."; "ble.errorcode.6 %@"="%@ Die App wird automatisch zum präferierten Gerät wiederverbinden, sobald es in Reichweite kommt."; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index b0ad7edc..dd6b1e2e 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -18,7 +18,6 @@ "available.radios"="Available Radios"; "automatic.detection"="Automatic Detection"; "battery.level"="Battery Level"; -"battery.level.trend"="Battery Level Trend"; "ble.name"="BLE Name"; "ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; "ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it comes back in range."; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index f0f54149..1fad751b 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -18,7 +18,6 @@ "available.radios"="可用的电台"; "automatic.detection"="自动识别"; "battery.level"="电池电量"; -"battery.level.trend"="电池电量趋势"; "ble.name"="蓝牙名称"; "ble.connection.timeout %d %@"="尝试连接%@失败,你可能需要在系统设置的蓝牙选项中忽略该电台。"; "ble.errorcode.6 %@"="%@ 如果在首选电台的旁边,App 将会自动重连。"; From 32b9afa657dd2c7fde934076262c6b9939adf9d1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 03:42:46 -0700 Subject: [PATCH 12/23] Chart Cleanup --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 39 ++++--------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 05e53f70..cdc25b71 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -19,7 +19,7 @@ struct DeviceMetricsLog: View { var body: some View { - let oneDayAgo = Calendar.current.date(byAdding: .hour, value: -6, to: Date()) + let oneDayAgo = Calendar.current.date(byAdding: .hour, value: -8, to: Date()) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] let chartData = deviceMetrics .filter { $0.time != nil && $0.time! >= oneDayAgo! } @@ -28,8 +28,7 @@ struct DeviceMetricsLog: View { NavigationStack { if chartData.count > 0 { - - GroupBox() {//label: Label("battery.level.trend", systemImage: "battery.100")) { + GroupBox(label: Label("8 Hour Trend - \(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { Chart(chartData, id: \.self) { @@ -37,55 +36,33 @@ struct DeviceMetricsLog: View { x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.batteryLevel) ) - .foregroundStyle(.blue) - PointMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), - y: .value("Value", $0.batteryLevel) - ) - .foregroundStyle(.blue) - -// LineMark( -// x: .value("Hour", $0.time!.formattedDate(format: "ha")), -// y: .value("Value", $0.channelUtilization) -// ) -// .foregroundStyle(.green) -// .interpolationMethod(.catmullRom) + .interpolationMethod(.linear) + .foregroundStyle(.indigo) + PointMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.channelUtilization) ) .foregroundStyle(.green) -// LineMark( -// x: .value("Time", $0.time!, unit: .hour), -// y: .value("Value", $0.airUtilTx) -// ) -// .foregroundStyle(.red) PointMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), - //x: .value("Time", $0.time!, unit: .hour), y: .value("Value", $0.airUtilTx) ) - .foregroundStyle(.red) + .foregroundStyle(.orange) } // Set color for each data in the chart .chartForegroundStyleScale([ "Battery Level" : .blue, - "Channel Utilization": .green, - "Airtime": .red + "Channel Utilization": .mint, + "Airtime": .orange ]) .chartLegend(position: .automatic, alignment: .bottom) -// .chartXAxis { -// AxisMarks(values: .stride(by: .hour)) -// } - //.frame(height: 200) } } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") - //let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] - Text("\(deviceMetrics.count) Readings") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { // Add a table for mac and ipad From ea762cec5f357ac4667169fdb155b1e131a7e53d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 03:46:32 -0700 Subject: [PATCH 13/23] Adjust chart colors --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index cdc25b71..c098d1ce 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -37,7 +37,7 @@ struct DeviceMetricsLog: View { y: .value("Value", $0.batteryLevel) ) .interpolationMethod(.linear) - .foregroundStyle(.indigo) + .foregroundStyle(.blue) PointMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), @@ -55,7 +55,7 @@ struct DeviceMetricsLog: View { // Set color for each data in the chart .chartForegroundStyleScale([ "Battery Level" : .blue, - "Channel Utilization": .mint, + "Channel Utilization": .green, "Airtime": .orange ]) .chartLegend(position: .automatic, alignment: .bottom) From fb320a9b02a137dcb6c6925fdea220653137f72b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 12:53:10 -0700 Subject: [PATCH 14/23] Alert Bell Characters for channels --- .../Views/Messages/ChannelMessageList.swift | 29 +++++++++++++++++-- en.lproj/Localizable.strings | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 3c44cb54..0934b690 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -233,16 +233,29 @@ struct ChannelMessageList: View { #if targetEnvironment(macCatalyst) HStack { Spacer() + + Button { + let bell = "🔔 Alert Bell Character! \u{7}" + print(bell) + typingMessage += bell + + } label: { + Text("Alert Bell") + Image(systemName: "bell.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.accentColor) + } + Spacer() Button { let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" sendPositionWithMessage = true if userSettings.meshtasticUsername.count > 0 { - typingMessage = "📍 " + userSettings.meshtasticUsername + " has shared their position with you from node " + userLongName + typingMessage += "📍 " + userSettings.meshtasticUsername + " has shared their position with you from node " + userLongName } else { - typingMessage = "📍 " + userLongName + " has shared their position with you." + typingMessage += "📍 " + userLongName + " has shared their position with you." } } label: { @@ -285,6 +298,18 @@ struct ChannelMessageList: View { } .font(.subheadline) Spacer() + Button { + let bell = "🔔 Alert Bell Character! \u{7}" + print(bell) + typingMessage += bell + + } label: { + Text("Alert") + Image(systemName: "bell.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.accentColor) + } + Spacer() Button { let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" sendPositionWithMessage = true diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index dd6b1e2e..53253d8f 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -68,7 +68,7 @@ "device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role."; "device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off."; "direct.messages"="Direct Messages"; -"dismiss.keyboard"="Dismiss Keyboard"; +"dismiss.keyboard"="Dismiss"; "display"="Display (Device Screen)"; "display.config"="Display Config"; "distance"="Distance"; From 3e88cb73d91a0a7483eab9487c2f71a6831a71f9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Apr 2023 15:01:11 -0700 Subject: [PATCH 15/23] Hook up re-broadcast mode --- Meshtastic/Views/Settings/Config/DeviceConfig.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 468e770d..d4cc6525 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -207,6 +207,7 @@ struct DeviceConfig: View { dc.debugLogEnabled = debugLogEnabled dc.buttonGpio = UInt32(buttonGPIO) dc.buzzerGpio = UInt32(buzzerGPIO) + dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue() let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { @@ -277,6 +278,13 @@ struct DeviceConfig: View { if newBuzzerGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true } } } + .onChange(of: rebroadcastMode) { newRebroadcastMode in + + if node != nil && node!.deviceConfig != nil { + + if newRebroadcastMode != node!.deviceConfig!.rebroadcastMode { hasChanges = true } + } + } } func setDeviceValues() { self.deviceRole = Int(node?.deviceConfig?.role ?? 0) @@ -284,6 +292,7 @@ struct DeviceConfig: View { self.debugLogEnabled = node?.deviceConfig?.debugLogEnabled ?? false self.buttonGPIO = Int(node?.deviceConfig?.buttonGpio ?? 0) self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0) + self.rebroadcastMode = Int(node?.deviceConfig?.rebroadcastMode ?? 0) self.hasChanges = false } } From f992d6e47abafc9b3b35ddbcc50d09173dbab525 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 2 Apr 2023 15:00:15 -0700 Subject: [PATCH 16/23] Hook up node info channel --- Meshtastic/Helpers/MeshPackets.swift | 4 ++-- Meshtastic/Persistence/UpdateCoreData.swift | 10 ++++++++-- Meshtastic/Views/Nodes/NodeList.swift | 9 +++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 4579033e..da1b2edd 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -251,7 +251,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje let newNode = NodeInfoEntity(context: context) newNode.id = Int64(nodeInfo.num) newNode.num = Int64(nodeInfo.num) - newNode.channel = Int32(channel) + newNode.channel = Int32(nodeInfo.channel) if nodeInfo.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) @@ -321,7 +321,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje 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) + fetchedNode[0].channel = Int32(nodeInfo.channel) if nodeInfo.hasUser { diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index d881e082..ed976443 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -117,7 +117,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.num = Int64(packet.from) newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) newNode.snr = packet.rxSnr - newNode.channel = Int32(packet.channel) + + if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { + newNode.channel = Int32(nodeInfoMessage.channel) + } + if let newUserMessage = try? User(serializedData: packet.decoded.payload) { let newUser = UserEntity(context: context) newUser.userId = newUserMessage.id @@ -134,9 +138,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) 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) + //fetchedNode[0].channel = Int32(packet.channel) if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { + + fetchedNode[0].channel = Int32(nodeInfoMessage.channel) if nodeInfoMessage.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 33e29fcd..c3d854ad 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -71,6 +71,15 @@ struct NodeList: View { LastHeardText(lastHeard: node.lastHeard) .font(.subheadline) } + if node.channel > 0 { + HStack(alignment: .bottom) { + Image(systemName: "fibrechannel") + .font(.title3) + .symbolRenderingMode(.hierarchical) + Text("Channel: \(node.channel)") + .font(.subheadline) + } + } } .frame(maxWidth: .infinity, alignment: .leading) } From b455fb25c6d2efb0bf555fcde3332dadbdb76b8a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 3 Apr 2023 17:48:58 -0700 Subject: [PATCH 17/23] Bump GPIO Pickers to 45 --- Meshtastic.xcodeproj/project.pbxproj | 12 + Meshtastic/Helpers/Extensions.swift | 1 + Meshtastic/Persistence/UpdateCoreData.swift | 1 - .../Views/Helpers/Node/NodeInfoView.swift | 285 ++++++++++++++++++ Meshtastic/Views/Nodes/NodeDetail.swift | 253 +--------------- Meshtastic/Views/Nodes/NodeList.swift | 14 +- .../Views/Settings/Config/DeviceConfig.swift | 4 +- .../Config/Module/CannedMessagesConfig.swift | 6 +- .../Module/ExternalNotificationConfig.swift | 6 +- .../Settings/Config/Module/SerialConfig.swift | 4 +- .../Settings/Config/PositionConfig.swift | 4 +- 11 files changed, 319 insertions(+), 271 deletions(-) create mode 100644 Meshtastic/Views/Helpers/Node/NodeInfoView.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 985c4c5f..6d830853 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -127,6 +127,7 @@ DDDE5A1129AFE69700490C6C /* MeshActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */; }; DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; + DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; @@ -305,6 +306,7 @@ DDDE5A0429AF163E00490C6C /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = ""; }; DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = ""; }; DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoView.swift; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; @@ -632,6 +634,7 @@ DDC2E18D26CE25CB0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + DDDEE5DF29DA3DA000A8E078 /* Node */, DD5E523D298F5A7D00D21B61 /* Weather */, DD47E3D526F17ED900029299 /* CircleText.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, @@ -689,6 +692,14 @@ path = Widgets; sourceTree = ""; }; + DDDEE5DF29DA3DA000A8E078 /* Node */ = { + isa = PBXGroup; + children = ( + DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */, + ); + path = Node; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -938,6 +949,7 @@ DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */, DD47E3CE26F103C600029299 /* NodeList.swift in Sources */, DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */, + DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */, DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */, DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */, DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 716eeb24..51a02608 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -45,6 +45,7 @@ extension UIColor { let red = CGFloat((hex & 0xFF0000) >> 16) let green = CGFloat((hex & 0x00FF00) >> 8) let blue = CGFloat((hex & 0x0000FF)) + //print("\(red) - \(green) - \(blue)") self.init(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1.0) } } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index ed976443..6bb33b13 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -138,7 +138,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) 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) { diff --git a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift new file mode 100644 index 00000000..2f9064ef --- /dev/null +++ b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift @@ -0,0 +1,285 @@ +// +// NodeInfoView.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 4/2/23. +// + +// +// DistanceText.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/19/22. +// + +import SwiftUI +import CoreLocation +import MapKit + +struct NodeInfoView: View { + + var node: NodeInfoEntity + + var body: some View { + + let hwModelString = node.user?.hwModel ?? "UNSET" + + Divider() + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + HStack { + VStack(alignment: .center) { + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 75, fontSize: 24, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) + } + Divider() + VStack { + if node.user != nil { + Image(hwModelString) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .cornerRadius(5) + + Text(String(hwModelString)) + .foregroundColor(.gray) + .font(.largeTitle).fixedSize() + } + } + + if node.snr > 0 { + Divider() + VStack(alignment: .center) { + + Image(systemName: "waveform.path") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + .padding(.bottom, 10) + Text("SNR").font(.largeTitle).fixedSize() + Text("\(String(format: "%.2f", node.snr)) dB") + .font(.largeTitle) + .foregroundColor(.gray) + .fixedSize() + } + } + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + if deviceMetrics?.count ?? 0 >= 1 { + + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity + Divider() + VStack(alignment: .center) { + BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) + if mostRecent?.voltage ?? 0 > 0.0 { + + Text(String(format: "%.2f", mostRecent?.voltage ?? 0.0) + " V") + .font(.title) + .foregroundColor(.gray) + .fixedSize() + } + } + .padding() + } + } + .padding() + + Divider() + HStack(alignment: .center) { + + VStack { + HStack { + Image(systemName: "person") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("user").font(.title)+Text(":").font(.title) + } + Text("!\(String(format: "%02x", node.num))") + .font(.title).foregroundColor(.gray) + } + Divider() + VStack { + HStack { + Image(systemName: "number") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Node Number:").font(.title) + } + Text(String(node.num)).font(.title).foregroundColor(.gray) + } + Divider() + VStack { + HStack { + Image(systemName: "globe") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("MAC Address: ").font(.title) + + } + Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")) + .font(.title) + .foregroundColor(.gray) + } + Divider() + VStack { + HStack { + Image(systemName: "clock.badge.checkmark.fill") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("heard.last").font(.title)+Text(":").font(.title) + + } + DateTimeText(dateTime: node.lastHeard) + .font(.title3) + .foregroundColor(.gray) + } + } + Divider() + + } else { + + HStack { + + VStack(alignment: .center) { + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) + } + Divider() + VStack { + if node.user != nil { + Image(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset")) + .resizable() + .frame(width: 75, height: 75) + .cornerRadius(5) + Text(String(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset"))) + .font(.callout).fixedSize() + } + } + + if node.snr > 0 { + Divider() + VStack(alignment: .center) { + + Image(systemName: "waveform.path") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("SNR").font(.title2).fixedSize() + Text("\(String(format: "%.2f", node.snr)) dB") + .font(.title2) + .foregroundColor(.gray) + .fixedSize() + } + } + + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + if deviceMetrics?.count ?? 0 >= 1 { + + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity + Divider() + VStack(alignment: .center) { + BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) + if mostRecent?.voltage ?? 0 > 0 { + + Text(String(format: "%.2f", mostRecent?.voltage ?? 0) + " V") + .font(.callout) + .foregroundColor(.gray) + .fixedSize() + } + } + } + } + Divider() + HStack(alignment: .center) { + VStack { + HStack { + Image(systemName: "person") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("User Id:").font(.title2) + } + Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray) + } + Divider() + VStack { + HStack { + Image(systemName: "number") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Node Number:").font(.title2) + } + Text(String(node.num)).font(.title3).foregroundColor(.gray) + } + } + Divider() + HStack { + Image(systemName: "globe") + .font(.headline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("MAC Address: ") + Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) + } + .padding([.bottom], 10) + Divider() + } + + VStack { + + if (node.positions?.count ?? 0) > 0 { + + NavigationLink { + PositionLog(node: node) + } label: { + + Image(systemName: "building.columns") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Position Log") + .font(.title3) + } + .fixedSize(horizontal: false, vertical: true) + Divider() + } + + if (node.telemetries?.count ?? 0) > 0 { + + NavigationLink { + DeviceMetricsLog(node: node) + } label: { + + Image(systemName: "flipphone") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Device Metrics Log") + .font(.title3) + } + Divider() + NavigationLink { + EnvironmentMetricsLog(node: node) + } label: { + + Image(systemName: "chart.xyaxis.line") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Environment Metrics Log") + .font(.title3) + } + Divider() + } + } + } +} +struct NodeInfoView_Previews: PreviewProvider { + static var previews: some View { + + VStack { + + } + } +} diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 525f094e..8508dfd7 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -140,257 +140,8 @@ struct NodeDetail: View { .padding([.top], 20) } - ScrollView { - Divider() - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - HStack { - VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 75, fontSize: 24, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) - } - Divider() - VStack { - if node.user != nil { - Image(hwModelString) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 100, height: 100) - .cornerRadius(5) - - Text(String(hwModelString)) - .foregroundColor(.gray) - .font(.largeTitle).fixedSize() - } - } - - if node.snr > 0 { - Divider() - VStack(alignment: .center) { - - Image(systemName: "waveform.path") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - .padding(.bottom, 10) - Text("SNR").font(.largeTitle).fixedSize() - Text("\(String(format: "%.2f", node.snr)) dB") - .font(.largeTitle) - .foregroundColor(.gray) - .fixedSize() - } - } - let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - if deviceMetrics?.count ?? 0 >= 1 { - - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - Divider() - VStack(alignment: .center) { - BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) - if mostRecent?.voltage ?? 0 > 0.0 { - - Text(String(format: "%.2f", mostRecent?.voltage ?? 0.0) + " V") - .font(.title) - .foregroundColor(.gray) - .fixedSize() - } - } - .padding() - } - } - .padding() - - Divider() - HStack(alignment: .center) { - - VStack { - HStack { - Image(systemName: "person") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("user").font(.title)+Text(":").font(.title) - } - Text("!\(String(format: "%02x", node.num))") - .font(.title).foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "number") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Node Number:").font(.title) - } - Text(String(node.num)).font(.title).foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "globe") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("MAC Address: ").font(.title) - - } - Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")) - .font(.title) - .foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "clock.badge.checkmark.fill") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("heard.last").font(.title)+Text(":").font(.title) - - } - DateTimeText(dateTime: node.lastHeard) - .font(.title3) - .foregroundColor(.gray) - } - } - Divider() - - } else { - - HStack { - - VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) - } - Divider() - VStack { - if node.user != nil { - Image(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset")) - .resizable() - .frame(width: 75, height: 75) - .cornerRadius(5) - Text(String(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset"))) - .font(.callout).fixedSize() - } - } - - if node.snr > 0 { - Divider() - VStack(alignment: .center) { - - Image(systemName: "waveform.path") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("SNR").font(.title2).fixedSize() - Text("\(String(format: "%.2f", node.snr)) dB") - .font(.title2) - .foregroundColor(.gray) - .fixedSize() - } - } - - let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - if deviceMetrics?.count ?? 0 >= 1 { - - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - Divider() - VStack(alignment: .center) { - BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) - if mostRecent?.voltage ?? 0 > 0 { - - Text(String(format: "%.2f", mostRecent?.voltage ?? 0) + " V") - .font(.callout) - .foregroundColor(.gray) - .fixedSize() - } - } - } - } - Divider() - HStack(alignment: .center) { - VStack { - HStack { - Image(systemName: "person") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("User Id:").font(.title2) - } - Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "number") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Node Number:").font(.title2) - } - Text(String(node.num)).font(.title3).foregroundColor(.gray) - } - } - Divider() - HStack { - Image(systemName: "globe") - .font(.headline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("MAC Address: ") - Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) - } - .padding([.bottom], 10) - Divider() - } - - VStack { - - if (node.positions?.count ?? 0) > 0 { - - NavigationLink { - PositionLog(node: node) - } label: { - - Image(systemName: "building.columns") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Position Log") - .font(.title3) - } - .fixedSize(horizontal: false, vertical: true) - Divider() - } - - if (node.telemetries?.count ?? 0) > 0 { - - NavigationLink { - DeviceMetricsLog(node: node) - } label: { - - Image(systemName: "flipphone") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Device Metrics Log") - .font(.title3) - } - Divider() - NavigationLink { - EnvironmentMetricsLog(node: node) - } label: { - - Image(systemName: "chart.xyaxis.line") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Environment Metrics Log") - .font(.title3) - } - Divider() - } - } - + ScrollView() { + NodeInfoView(node: node) if self.bleManager.connectedPeripheral != nil && node.metadata != nil { HStack { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c3d854ad..c2c96d68 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -64,13 +64,6 @@ struct NodeList: View { } } } - HStack(alignment: .bottom) { - Image(systemName: "clock.badge.checkmark.fill") - .font(.title3) - .symbolRenderingMode(.hierarchical) - LastHeardText(lastHeard: node.lastHeard) - .font(.subheadline) - } if node.channel > 0 { HStack(alignment: .bottom) { Image(systemName: "fibrechannel") @@ -80,6 +73,13 @@ struct NodeList: View { .font(.subheadline) } } + HStack(alignment: .bottom) { + Image(systemName: "clock.badge.checkmark.fill") + .font(.title3) + .symbolRenderingMode(.hierarchical) + LastHeardText(lastHeard: node.lastHeard) + .font(.subheadline) + } } .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index d4cc6525..e9e5406c 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -100,7 +100,7 @@ struct DeviceConfig: View { Section(header: Text("GPIO")) { Picker("Button GPIO", selection: $buttonGPIO) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -110,7 +110,7 @@ struct DeviceConfig: View { } .pickerStyle(DefaultPickerStyle()) Picker("Buzzer GPIO", selection: $buzzerGPIO) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 38debac0..8e2ac4d0 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -125,7 +125,7 @@ struct CannedMessagesConfig: View { .disabled(configPreset > 0) Section(header: Text("Inputs")) { Picker("Pin A", selection: $inputbrokerPinA) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -137,7 +137,7 @@ struct CannedMessagesConfig: View { Text("GPIO pin for rotary encoder A port.") .font(.caption) Picker("Pin B", selection: $inputbrokerPinB) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -149,7 +149,7 @@ struct CannedMessagesConfig: View { Text("GPIO pin for rotary encoder B port.") .font(.caption) Picker("Press Pin", selection: $inputbrokerPinPress) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 2765b46d..b34189d8 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -92,7 +92,7 @@ struct ExternalNotificationConfig: View { Text("If enabled, the 'output' Pin will be pulled active high, disabled means active low.") .font(.caption) Picker("Output pin GPIO", selection: $output) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -140,7 +140,7 @@ struct ExternalNotificationConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Picker("Output pin buzzer GPIO ", selection: $outputBuzzer) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -150,7 +150,7 @@ struct ExternalNotificationConfig: View { } .pickerStyle(DefaultPickerStyle()) Picker("Output pin vibra GPIO", selection: $outputVibra) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 6f5cc238..6a2a49b7 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -98,7 +98,7 @@ struct SerialConfig: View { Section(header: Text("GPIO")) { Picker("Receive data (rxd) GPIO pin", selection: $rxd) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -109,7 +109,7 @@ struct SerialConfig: View { .pickerStyle(DefaultPickerStyle()) Picker("Transmit data (txd) GPIO pin", selection: $txd) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 55d61401..b00366a8 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -236,7 +236,7 @@ struct PositionConfig: View { .font(.caption) Picker("GPS Receive GPIO", selection: $rxGpio) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { @@ -247,7 +247,7 @@ struct PositionConfig: View { .pickerStyle(DefaultPickerStyle()) Picker("GPS Transmit GPIO", selection: $txGpio) { - ForEach(0..<40) { + ForEach(0..<46) { if $0 == 0 { Text("unset") } else { From 6ef4f63189af96928cbc62e6978ab27513353630 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Apr 2023 15:44:18 -0700 Subject: [PATCH 18/23] Add Route core data structures --- Meshtastic.xcodeproj/project.pbxproj | 4 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 329 ++++++++++++++++++ 3 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6d830853..2f7851d6 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -307,6 +307,7 @@ DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = ""; }; DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoView.swift; sourceTree = ""; }; + DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; @@ -1492,6 +1493,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */, DDC94FC329CED7280082EA6E /* MeshtasticDataModelV10.xcdatamodel */, DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */, DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */, @@ -1503,7 +1505,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDC94FC329CED7280082EA6E /* MeshtasticDataModelV10.xcdatamodel */; + currentVersion = DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index ff97f8c7..f9b5a5e7 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV10.xcdatamodel + MeshtasticDataModelV11.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents new file mode 100644 index 00000000..bd103e74 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f1e93733ff23875f4841fbf2d33bafeaf87b20ea Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Apr 2023 19:29:03 -0700 Subject: [PATCH 19/23] Clean up device metrics log --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index c098d1ce..a99d98a4 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -8,58 +8,60 @@ import SwiftUI import Charts struct DeviceMetricsLog: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" var node: NodeInfoEntity - + var body: some View { - let oneDayAgo = Calendar.current.date(byAdding: .hour, value: -8, to: Date()) + let oneDayAgo = Calendar.current.date(byAdding: .hour, value: -12, to: Date()) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] let chartData = deviceMetrics .filter { $0.time != nil && $0.time! >= oneDayAgo! } .sorted { $0.time! < $1.time! } - + NavigationStack { - if chartData.count > 0 { - GroupBox(label: Label("8 Hour Trend - \(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - + if chartData.count > 0 { + GroupBox(label: Label("8 Hour Trend - \(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { + Chart(chartData, id: \.self) { LineMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.batteryLevel) ) - .interpolationMethod(.linear) + .interpolationMethod(.cardinal) + .foregroundStyle(.blue) + PointMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) .foregroundStyle(.blue) - PointMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.channelUtilization) ) .foregroundStyle(.green) - PointMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.airUtilTx) ) .foregroundStyle(.orange) - } - // Set color for each data in the chart - .chartForegroundStyleScale([ + .chartForegroundStyleScale([ "Battery Level" : .blue, "Channel Utilization": .green, "Airtime": .orange - ]) - .chartLegend(position: .automatic, alignment: .bottom) + ]) + .chartLegend(position: .automatic, alignment: .bottom) } + .frame(height: 225) } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") @@ -68,7 +70,6 @@ struct DeviceMetricsLog: View { // Add a table for mac and ipad //Table(Array(deviceMetrics),id: \.self) { Table(deviceMetrics) { - TableColumn("battery.level") { dm in if dm.batteryLevel > 100 { Text("Powered") @@ -89,10 +90,8 @@ struct DeviceMetricsLog: View { Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } } - } else { ScrollView { - let columns = [ GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), @@ -118,7 +117,6 @@ struct DeviceMetricsLog: View { .font(.caption) .fontWeight(.bold) } - ForEach(deviceMetrics) { dm in GridRow { if dm.batteryLevel > 100 { @@ -134,7 +132,6 @@ struct DeviceMetricsLog: View { .font(.caption) Text("\(String(format: "%.2f", dm.airUtilTx))%") .font(.caption) - Text(dm.time?.formattedDate(format: dateFormatString) ?? "Unknown time") .font(.caption2) } @@ -197,7 +194,6 @@ struct DeviceMetricsLog: View { if case .success = result { print("Device metrics log download succeeded.") self.isExporting = false - } else { print("Device metrics log download failed: \(result).") } From d2b0abee86c6f0dcaf28be7f2cf3f0b4b41fac32 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Apr 2023 20:14:54 -0700 Subject: [PATCH 20/23] Try and clear user default values on clear app settings --- Meshtastic/Helpers/Extensions.swift | 22 +++++++++++++++++++ Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 2 +- Meshtastic/Views/Settings/AppSettings.swift | 2 ++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 51a02608..f4b6a0ae 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -165,3 +165,25 @@ extension String { } } } + +extension UserDefaults { + + enum Keys: String, CaseIterable { + case meshtasticUsername + case preferredPeripheralId + case provideLocation + case provideLocationInterval + case keyboardType + case meshMapType + case meshMapCenteringMode + case meshMapRecentering + case meshMapCustomTileServer + case meshMapUserTrackingMode + case meshMapShowNodeHistory + case meshMapShowRouteLines + } + + func reset() { + Keys.allCases.forEach { removeObject(forKey: $0.rawValue) } + } +} diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index a99d98a4..13c41f31 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -36,7 +36,7 @@ struct DeviceMetricsLog: View { x: .value("Hour", $0.time!.formattedDate(format: "ha")), y: .value("Value", $0.batteryLevel) ) - .interpolationMethod(.cardinal) + .interpolationMethod(.linear) .foregroundStyle(.blue) PointMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index ec83fae9..dae61991 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -117,6 +117,8 @@ struct AppSettings: View { Button("Erase all app data?", role: .destructive) { bleManager.disconnectPeripheral() clearCoreDataDatabase(context: context) + UserDefaults.standard.reset() + UserDefaults.standard.synchronize() } } } From 793290680bf5ae03d6c1658b2d814fca467344af Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Apr 2023 23:34:34 -0700 Subject: [PATCH 21/23] Improve device metrics chart --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 13c41f31..af91b9ce 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -15,45 +15,71 @@ struct DeviceMetricsLog: View { @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" + + @State private var batteryChartColor: Color = .blue + @State private var airtimeChartColor: Color = .orange + @State private var channelUtilizationChartColor: Color = .green var node: NodeInfoEntity var body: some View { - let oneDayAgo = Calendar.current.date(byAdding: .hour, value: -12, to: Date()) + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -1, to: Date()) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] let chartData = deviceMetrics - .filter { $0.time != nil && $0.time! >= oneDayAgo! } - .sorted { $0.time! < $1.time! } - + .filter { $0.time != nil && $0.time! >= oneDayAgo! } + .sorted { $0.time! < $1.time! } + NavigationStack { if chartData.count > 0 { - GroupBox(label: Label("8 Hour Trend - \(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { + GroupBox(label: Label("\(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - Chart(chartData, id: \.self) { + Chart { - LineMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), - y: .value("Value", $0.batteryLevel) - ) - .interpolationMethod(.linear) - .foregroundStyle(.blue) - PointMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), - y: .value("Value", $0.batteryLevel) - ) - .foregroundStyle(.blue) - PointMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), - y: .value("Value", $0.channelUtilization) - ) - .foregroundStyle(.green) - PointMark( - x: .value("Hour", $0.time!.formattedDate(format: "ha")), - y: .value("Value", $0.airUtilTx) - ) - .foregroundStyle(.orange) + ForEach(chartData, id: \.self) { point in + + Plot { + LineMark( + x: .value("x", point.time!), + y: .value("y", point.batteryLevel) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") + .foregroundStyle(batteryChartColor) + + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.channelUtilization) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") + .foregroundStyle(channelUtilizationChartColor) + + RuleMark(y: .value("Limit", 10)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(airtimeChartColor) + + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.airUtilTx) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") + .foregroundStyle(airtimeChartColor) + } } + .chartXAxis(content: { + AxisMarks(position: .top) + }) + //.chartYAxis(.hidden) + //.frame(height: 400) + //.padding() + .chartXAxis(.automatic) .chartForegroundStyleScale([ "Battery Level" : .blue, "Channel Utilization": .green, @@ -61,7 +87,7 @@ struct DeviceMetricsLog: View { ]) .chartLegend(position: .automatic, alignment: .bottom) } - .frame(height: 225) + .frame(minHeight: 250) } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") From 09e9e234aae8c72aeb69b9b97b2cc9d19969e948 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Apr 2023 23:36:24 -0700 Subject: [PATCH 22/23] Remove commented out code --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index af91b9ce..1de2541c 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -76,9 +76,6 @@ struct DeviceMetricsLog: View { .chartXAxis(content: { AxisMarks(position: .top) }) - //.chartYAxis(.hidden) - //.frame(height: 400) - //.padding() .chartXAxis(.automatic) .chartForegroundStyleScale([ "Battery Level" : .blue, From bcb8eb1c720e2a78e34a98f900e6ab5604cbf5c3 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Apr 2023 12:10:31 -0700 Subject: [PATCH 23/23] Update protos, add fields to core data --- .../MeshtasticDataModelV11.xcdatamodel/contents | 3 +++ Meshtastic/Protobufs/meshtastic/channel.pb.swift | 10 +++------- Meshtastic/Protobufs/meshtastic/config.pb.swift | 10 ++++++++++ Meshtastic/Protobufs/meshtastic/module_config.pb.swift | 10 ++++++++++ Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 1 + 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents index bd103e74..689184f3 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV11.xcdatamodel/contents @@ -43,6 +43,7 @@ + @@ -149,6 +150,7 @@ + @@ -253,6 +255,7 @@ + diff --git a/Meshtastic/Protobufs/meshtastic/channel.pb.swift b/Meshtastic/Protobufs/meshtastic/channel.pb.swift index f635dddd..a3a89d0d 100644 --- a/Meshtastic/Protobufs/meshtastic/channel.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/channel.pb.swift @@ -21,11 +21,9 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP } /// -/// Full settings (center freq, spread factor, pre-shared secret key etc...) -/// needed to configure a radio for speaking on a particular channel This -/// information can be encoded as a QRcode/url so that other users can configure +/// This information can be encoded as a QRcode/url so that other users can configure /// their radio to join the same channel. -/// A note about how channel names are shown to users: channelname-Xy +/// A note about how channel names are shown to users: channelname-X /// poundsymbol is a prefix used to indicate this is a channel name (idea from @professr). /// Where X is a letter from A-Z (base 26) representing a hash of the PSK for this /// channel - so that if the user changes anything about the channel (which does @@ -35,8 +33,6 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// The PSK is hashed into this letter by "0x41 + [xor all bytes of the psk ] modulo 26" /// This also allows the option of someday if people have the PSK off (zero), the /// users COULD type in a channel name and be able to talk. -/// Y is a lower case letter from a-z that represents the channel 'speed' settings -/// (for some future definition of speed) /// FIXME: Add description of multi-channel support and how primary vs secondary channels are used. /// FIXME: explain how apps use channels for security. /// explain how remote settings and remote gpio are managed as an example @@ -57,7 +53,7 @@ struct ChannelSettings { /// because they are listed in this source code. /// Those bytes are mapped using the following scheme: /// `0` = No crypto - /// `1` = The special "default" channel key: {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf} + /// `1` = The special "default" channel key: {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01} /// `2` through 10 = The default channel key, except with 1 through 9 added to the last byte. /// Shown to user as simple1 through 10 var psk: Data = Data() diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index 4f92bedd..8c502d60 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -177,6 +177,10 @@ struct Config { /// Defaults to 900 Seconds (15 minutes) var nodeInfoBroadcastSecs: UInt32 = 0 + /// + /// Treat double tap interrupt on supported accelerometers as a button press if set to true + var doubleTapAsButtonPress: Bool = false + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -1587,6 +1591,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl 5: .standard(proto: "buzzer_gpio"), 6: .standard(proto: "rebroadcast_mode"), 7: .standard(proto: "node_info_broadcast_secs"), + 8: .standard(proto: "double_tap_as_button_press"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1602,6 +1607,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl case 5: try { try decoder.decodeSingularUInt32Field(value: &self.buzzerGpio) }() case 6: try { try decoder.decodeSingularEnumField(value: &self.rebroadcastMode) }() case 7: try { try decoder.decodeSingularUInt32Field(value: &self.nodeInfoBroadcastSecs) }() + case 8: try { try decoder.decodeSingularBoolField(value: &self.doubleTapAsButtonPress) }() default: break } } @@ -1629,6 +1635,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if self.nodeInfoBroadcastSecs != 0 { try visitor.visitSingularUInt32Field(value: self.nodeInfoBroadcastSecs, fieldNumber: 7) } + if self.doubleTapAsButtonPress != false { + try visitor.visitSingularBoolField(value: self.doubleTapAsButtonPress, fieldNumber: 8) + } try unknownFields.traverse(visitor: &visitor) } @@ -1640,6 +1649,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if lhs.buzzerGpio != rhs.buzzerGpio {return false} if lhs.rebroadcastMode != rhs.rebroadcastMode {return false} if lhs.nodeInfoBroadcastSecs != rhs.nodeInfoBroadcastSecs {return false} + if lhs.doubleTapAsButtonPress != rhs.doubleTapAsButtonPress {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift index 914b5f9f..5c5f151c 100644 --- a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift @@ -242,6 +242,10 @@ struct ModuleConfig { /// Whether to send / consume json packets on MQTT var jsonEnabled: Bool = false + /// + /// If true, we attempt to establish a secure connection using TLS + var tlsEnabled: Bool = false + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -1109,6 +1113,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message 4: .same(proto: "password"), 5: .standard(proto: "encryption_enabled"), 6: .standard(proto: "json_enabled"), + 7: .standard(proto: "tls_enabled"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1123,6 +1128,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message case 4: try { try decoder.decodeSingularStringField(value: &self.password) }() case 5: try { try decoder.decodeSingularBoolField(value: &self.encryptionEnabled) }() case 6: try { try decoder.decodeSingularBoolField(value: &self.jsonEnabled) }() + case 7: try { try decoder.decodeSingularBoolField(value: &self.tlsEnabled) }() default: break } } @@ -1147,6 +1153,9 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message if self.jsonEnabled != false { try visitor.visitSingularBoolField(value: self.jsonEnabled, fieldNumber: 6) } + if self.tlsEnabled != false { + try visitor.visitSingularBoolField(value: self.tlsEnabled, fieldNumber: 7) + } try unknownFields.traverse(visitor: &visitor) } @@ -1157,6 +1166,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message if lhs.password != rhs.password {return false} if lhs.encryptionEnabled != rhs.encryptionEnabled {return false} if lhs.jsonEnabled != rhs.jsonEnabled {return false} + if lhs.tlsEnabled != rhs.tlsEnabled {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 1de2541c..8451da12 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -47,6 +47,7 @@ struct DeviceMetricsLog: View { .accessibilityLabel("Line Series") .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") .foregroundStyle(batteryChartColor) + .interpolationMethod(.cardinal) Plot { PointMark(