Added initial detection sensor log

This commit is contained in:
Ben Meadors 2023-08-23 07:03:05 -05:00
parent c9d9a38c91
commit 3275bbf348
9 changed files with 240 additions and 2 deletions

View file

@ -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 */,

View file

@ -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)

View 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))
}
}

View file

@ -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 []
}
}

View file

@ -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()
}
}
}

View file

@ -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)
}
}

View 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()
// }
//}

View file

@ -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
View 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