mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Added initial detection sensor log
This commit is contained in:
parent
c9d9a38c91
commit
3275bbf348
9 changed files with 240 additions and 2 deletions
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; };
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; };
|
||||
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; };
|
||||
C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; };
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; };
|
||||
|
|
@ -197,6 +199,8 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = "<group>"; };
|
||||
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = "<group>"; };
|
||||
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
|
||||
A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
|
||||
DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -433,6 +437,7 @@
|
|||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
|
||||
DD73FD1028750779000852D6 /* PositionLog.swift */,
|
||||
DD14E72D2A82A614006E39BC /* RemoteHardware.swift */,
|
||||
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */,
|
||||
);
|
||||
path = Nodes;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -746,6 +751,7 @@
|
|||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */,
|
||||
DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */,
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */,
|
||||
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */,
|
||||
);
|
||||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1009,6 +1015,7 @@
|
|||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */,
|
||||
DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */,
|
||||
|
|
@ -1039,6 +1046,7 @@
|
|||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
DD5E5209298EE33B00D21B61 /* module_config.pb.swift in Sources */,
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */,
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
|
||||
DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */,
|
||||
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,21 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
return csvString
|
||||
}
|
||||
|
||||
func detectionsToCsv(detections: [MessageEntity]) -> String {
|
||||
var csvString: String = ""
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
|
||||
// Create Header
|
||||
csvString = "Detection event, \("timestamp".localized)"
|
||||
for d in detections {
|
||||
csvString += "\n"
|
||||
csvString += d.messagePayload ?? "Detection"
|
||||
csvString += ", "
|
||||
csvString += d.timestamp.formattedDate(format: dateFormatString).localized
|
||||
}
|
||||
return csvString
|
||||
}
|
||||
|
||||
func positionToCsvFile(positions: [PositionEntity]) -> String {
|
||||
var csvString: String = ""
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
|
|
|
|||
21
Meshtastic/Persistence/MessageEntityExtension.swift
Normal file
21
Meshtastic/Persistence/MessageEntityExtension.swift
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// MessageEntityExtension.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Ben on 8/22/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import CoreData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
extension MessageEntity {
|
||||
|
||||
var timestamp: Date {
|
||||
let time = messageTimestamp <= 0 ? receivedTimestamp : messageTimestamp
|
||||
return Date(timeIntervalSince1970: TimeInterval(time))
|
||||
}
|
||||
}
|
||||
|
|
@ -60,3 +60,23 @@ public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointE
|
|||
}
|
||||
return WaypointEntity(context: context)
|
||||
}
|
||||
|
||||
|
||||
public func getDetectionSensorMessages(nodeNum: Int64?, context: NSManagedObjectContext) -> [MessageEntity] {
|
||||
|
||||
let fetchDetectionMessagesPredicate: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
|
||||
fetchDetectionMessagesPredicate.predicate = NSPredicate(format: "portNum == %d", Int32(PortNum.detectionSensorApp.rawValue))
|
||||
|
||||
do {
|
||||
let fetched = try context.fetch(fetchDetectionMessagesPredicate) as? [MessageEntity] ?? []
|
||||
if nodeNum == nil {
|
||||
return fetched.reversed()
|
||||
}
|
||||
return fetched.filter { message in
|
||||
return message.fromUser?.num == nodeNum!
|
||||
}.reversed()
|
||||
}
|
||||
catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,19 @@ struct NodeInfoView: View {
|
|||
}
|
||||
Divider()
|
||||
}
|
||||
NavigationLink {
|
||||
DetectionSensorLog(node: node)
|
||||
} label: {
|
||||
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.title)
|
||||
|
||||
Text("Detection Sensor Log")
|
||||
.font(.title3)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,8 +198,7 @@ struct ChannelMessageList: View {
|
|||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2).foregroundColor(.red)
|
||||
} else if isDetectionSensorMessage {
|
||||
let timeStamp = message.messageTimestamp <= 0 ? message.receivedTimestamp : message.messageTimestamp
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(timeStamp))
|
||||
let messageDate = message.timestamp
|
||||
Text(" \(messageDate.formattedDate(format: dateFormatString))").font(.caption2).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
157
Meshtastic/Views/Nodes/DetectionSensorLog.swift
Normal file
157
Meshtastic/Views/Nodes/DetectionSensorLog.swift
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
//
|
||||
// DetectionSensorLog.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Ben on 8/22/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Charts
|
||||
|
||||
struct DetectionSensorLog: 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: .day, value: -1, to: Date())
|
||||
let detections = getDetectionSensorMessages(nodeNum: node.num, context: context)
|
||||
let chartData = detections
|
||||
.filter { $0.timestamp >= oneDayAgo! }
|
||||
.sorted { $0.timestamp < $1.timestamp }
|
||||
|
||||
NavigationStack {
|
||||
|
||||
if chartData.count > 0 {
|
||||
GroupBox(label: Label("\(detections.count) Total Detection Events", systemImage: "sensor")) {
|
||||
|
||||
Chart {
|
||||
ForEach(chartData, id: \.self) { point in
|
||||
Plot {
|
||||
BarMark(
|
||||
x: .value("x", point.timestamp),
|
||||
y: .value("y", 1)
|
||||
)
|
||||
}
|
||||
.accessibilityLabel("Bar Series")
|
||||
.accessibilityValue("X: \(point.timestamp), Y: \(1)")
|
||||
.interpolationMethod(.cardinal)
|
||||
.foregroundStyle(
|
||||
.linearGradient(
|
||||
colors: [.green, .yellow, .orange, .red],
|
||||
startPoint: .bottom,
|
||||
endPoint: .top
|
||||
)
|
||||
)
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}
|
||||
.chartXAxis(content: {
|
||||
AxisMarks(position: .top)
|
||||
// AxisMarks(position: .top, values: .stride(by: .hour)) { date in
|
||||
// AxisValueLabel(format: .dateTime.hour())
|
||||
// }
|
||||
})
|
||||
.chartXAxis(.automatic)
|
||||
.chartYScale(domain: 0...20)
|
||||
.chartForegroundStyleScale([
|
||||
"Detection events" : .green,
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
}
|
||||
.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: "")
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
|
||||
// Add a table for mac and ipad
|
||||
Table(detections) {
|
||||
TableColumn("Detection event") { d in
|
||||
Text(d.messagePayload ?? "Detected")
|
||||
}
|
||||
|
||||
TableColumn("timestamp") { d in
|
||||
Text(d.timestamp.formattedDate(format: dateFormatString))
|
||||
}
|
||||
.width(min: 180)
|
||||
}
|
||||
} else {
|
||||
ScrollView {
|
||||
let columns = [
|
||||
GridItem(),
|
||||
GridItem()
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
GridRow {
|
||||
Text("Detection")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("timestamp")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
ForEach(detections) { d in
|
||||
GridRow {
|
||||
Text(d.messagePayload ?? "Detected")
|
||||
Text(d.timestamp.formattedDate(format: dateFormatString))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.leading, 15)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
exportString = detectionsToCsv(detections: chartData)
|
||||
isExporting = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
}
|
||||
.navigationTitle("detection.sensor.log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("detection.sensor.log".localized)"),
|
||||
onCompletion: { result in
|
||||
if case .success = result {
|
||||
print("Detections metrics log download succeeded.")
|
||||
self.isExporting = false
|
||||
} else {
|
||||
print("Detections log download failed: \(result).")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
//
|
||||
//struct DetectionSensorLog_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// DetectionSensorLog()
|
||||
// }
|
||||
//}
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
"delete"="Delete";
|
||||
"detection.sensor"="Detection Sensor";
|
||||
"detection.sensor.config"="Detection Sensor Config";
|
||||
"detection.sensor.log"="Detection Sensor Log";
|
||||
"device"="Device";
|
||||
"device.config"="Device Config";
|
||||
"device.metrics.delete"="Delete all device metrics?";
|
||||
|
|
|
|||
4
unthebenternify.sh
Executable file
4
unthebenternify.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
sed -i '' -e 's/6YF6QJH524/GCH7VS5Y9R/g' ./Meshtastic.xcodeproj/project.pbxproj
|
||||
sed -i '' -e 's/thebentern.Meshtastic/gvh.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj
|
||||
Loading…
Add table
Add a link
Reference in a new issue