2022-07-15 15:01:42 -07:00
|
|
|
//
|
|
|
|
|
// WriteCsvFile.swift
|
|
|
|
|
// Meshtastic
|
|
|
|
|
//
|
|
|
|
|
// Copyright(c) Garth Vander Houwen 7/15/22.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import SwiftUI
|
2024-06-23 07:09:14 -07:00
|
|
|
import OSLog
|
2022-07-15 15:01:42 -07:00
|
|
|
|
2025-09-09 20:24:44 -04:00
|
|
|
func telemetryToCsvFile<S: Sequence>(telemetry: S, metricsType: Int) -> String where S.Element == TelemetryEntity {
|
2022-07-15 15:01:42 -07:00
|
|
|
var csvString: String = ""
|
2022-12-31 00:26:59 -08:00
|
|
|
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
2023-01-12 07:57:24 -08:00
|
|
|
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
|
2022-07-15 15:01:42 -07:00
|
|
|
if metricsType == 0 {
|
|
|
|
|
// Create Device Metrics Header
|
2025-04-30 08:19:30 -07:00
|
|
|
csvString = "\("battery.level".localized), \("Voltage".localized), \("Channel Utilization".localized), \("airtime".localized), \("Uptime".localized), \("Timestamp".localized)"
|
2024-06-02 18:32:14 -07:00
|
|
|
for dm in telemetry where dm.metricsType == 0 {
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += "\n"
|
|
|
|
|
csvString += dm.batteryLevel?.formatted(.number.grouping(.never)) ?? ""
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += dm.voltage?.formatted(.number.grouping(.never)) ?? ""
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += dm.channelUtilization?.formatted(.number.grouping(.never)) ?? ""
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += dm.airUtilTx?.formatted(.number.grouping(.never)) ?? ""
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += dm.uptimeSeconds?.formatted(.number.grouping(.never)) ?? ""
|
|
|
|
|
csvString += ", "
|
2025-05-03 08:58:33 -07:00
|
|
|
csvString += dm.time?.formattedDate(format: dateFormatString) ?? "Unknown Age".localized
|
2022-07-15 15:01:42 -07:00
|
|
|
}
|
2022-09-30 17:10:03 -07:00
|
|
|
} else if metricsType == 1 {
|
2022-08-01 07:11:03 -07:00
|
|
|
// Create Environment Telemetry Header
|
2025-02-15 10:06:54 -08:00
|
|
|
csvString = "Temperature, Relative Humidity, Barometric Pressure, Indoor Air Quality, Gas Resistance, \("Timestamp".localized)"
|
2024-06-02 18:32:14 -07:00
|
|
|
for dm in telemetry where dm.metricsType == 1 {
|
|
|
|
|
csvString += "\n"
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.temperature?.formatted(.number.grouping(.never)) ?? ""
|
2024-06-02 18:32:14 -07:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.relativeHumidity?.formatted(.number.grouping(.never)) ?? ""
|
2024-06-02 18:32:14 -07:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.barometricPressure?.formatted(.number.grouping(.never)) ?? ""
|
2024-06-02 18:32:14 -07:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.iaq?.formatted(.number.grouping(.never)) ?? ""
|
2024-06-02 18:32:14 -07:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.gasResistance?.formatted(.number.grouping(.never)) ?? ""
|
2024-06-02 18:32:14 -07:00
|
|
|
csvString += ", "
|
2025-05-03 08:58:33 -07:00
|
|
|
csvString += dm.time?.formattedDate(format: dateFormatString) ?? "Unknown Age".localized
|
2022-08-01 07:11:03 -07:00
|
|
|
}
|
2025-01-25 10:58:32 -08:00
|
|
|
} else if metricsType == 2 {
|
|
|
|
|
// Create Power Metrics Header
|
2025-02-15 10:06:54 -08:00
|
|
|
csvString = "Channel 1 Voltage, Channel 1 Current, Channel 2 Voltage, Channel 2 Current, Channel 3 Voltage, Channel 3 Current, \("Timestamp".localized)"
|
2025-01-25 10:58:32 -08:00
|
|
|
for dm in telemetry where dm.metricsType == 2 {
|
|
|
|
|
csvString += "\n"
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.powerCh1Voltage?.formatted(.number.grouping(.never)) ?? ""
|
2025-01-25 10:58:32 -08:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.powerCh1Current?.formatted(.number.grouping(.never)) ?? ""
|
2025-01-25 10:58:32 -08:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.powerCh2Voltage?.formatted(.number.grouping(.never)) ?? ""
|
2025-01-25 10:58:32 -08:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.powerCh2Current?.formatted(.number.grouping(.never)) ?? ""
|
2025-01-25 10:58:32 -08:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.powerCh3Voltage?.formatted(.number.grouping(.never)) ?? ""
|
2025-01-25 10:58:32 -08:00
|
|
|
csvString += ", "
|
2025-02-21 18:23:03 -05:00
|
|
|
csvString += dm.powerCh3Current?.formatted(.number.grouping(.never)) ?? ""
|
2025-01-25 10:58:32 -08:00
|
|
|
csvString += ", "
|
2025-05-03 08:58:33 -07:00
|
|
|
csvString += dm.time?.formattedDate(format: dateFormatString) ?? "Unknown Age".localized
|
2025-01-25 10:58:32 -08:00
|
|
|
}
|
2022-07-15 15:01:42 -07:00
|
|
|
}
|
|
|
|
|
return csvString
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-23 07:03:05 -05:00
|
|
|
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
|
2025-02-15 10:06:54 -08:00
|
|
|
csvString = "Detection event, \("Timestamp".localized)"
|
2023-08-23 07:03:05 -05:00
|
|
|
for d in detections {
|
|
|
|
|
csvString += "\n"
|
|
|
|
|
csvString += d.messagePayload ?? "Detection"
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += d.timestamp.formattedDate(format: dateFormatString).localized
|
|
|
|
|
}
|
|
|
|
|
return csvString
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 07:09:14 -07:00
|
|
|
func logToCsvFile(log: [OSLogEntryLog]) -> 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 PAX Header
|
2025-02-15 10:06:54 -08:00
|
|
|
csvString = "Process, Category, Level, Message, \("Timestamp".localized)"
|
2024-06-23 07:09:14 -07:00
|
|
|
for l in log {
|
|
|
|
|
csvString += "\n"
|
|
|
|
|
csvString += String(l.process)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(l.category)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(l.level.description)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(l.composedMessage)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += l.date.formattedDate(format: dateFormatString)
|
|
|
|
|
}
|
|
|
|
|
return csvString
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-25 21:40:25 -08:00
|
|
|
func paxToCsvFile(pax: [PaxCounterEntity]) -> 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 PAX Header
|
2025-02-15 10:06:54 -08:00
|
|
|
csvString = "BLE, WiFi, Total Pax, Uptime, \("Timestamp".localized)"
|
2024-02-25 21:40:25 -08:00
|
|
|
for p in pax {
|
|
|
|
|
csvString += "\n"
|
|
|
|
|
csvString += String(p.ble)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(p.wifi)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(p.ble + p.wifi)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(p.uptime)
|
|
|
|
|
csvString += ", "
|
2025-05-03 08:58:33 -07:00
|
|
|
csvString += p.time?.formattedDate(format: dateFormatString) ?? "Unknown Age".localized
|
2024-02-25 21:40:25 -08:00
|
|
|
}
|
|
|
|
|
return csvString
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-06 10:33:18 -08:00
|
|
|
func positionToCsvFile(positions: [PositionEntity]) -> String {
|
2022-07-15 15:01:42 -07:00
|
|
|
var csvString: String = ""
|
2022-12-31 00:26:59 -08:00
|
|
|
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
2023-01-12 11:01:10 -08:00
|
|
|
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
|
2022-07-15 15:01:42 -07:00
|
|
|
// Create Position Header
|
2025-02-15 10:06:54 -08:00
|
|
|
csvString = "SeqNo, Latitude, Longitude, Altitude, Sats, Speed, Heading, SNR, \("Timestamp".localized)"
|
2022-07-15 15:01:42 -07:00
|
|
|
for pos in positions {
|
|
|
|
|
csvString += "\n"
|
2022-10-02 10:36:14 -07:00
|
|
|
csvString += String(pos.seqNo)
|
|
|
|
|
csvString += ", "
|
2022-10-03 16:52:00 -07:00
|
|
|
csvString += String((pos.latitude ?? 0))
|
2022-07-15 15:01:42 -07:00
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(pos.longitude ?? 0)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(pos.altitude)
|
|
|
|
|
csvString += ", "
|
2022-09-30 20:25:41 -07:00
|
|
|
csvString += String(pos.satsInView)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(pos.speed)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(pos.heading)
|
|
|
|
|
csvString += ", "
|
2022-11-10 23:27:48 -08:00
|
|
|
csvString += String(pos.snr)
|
|
|
|
|
csvString += ", "
|
2025-05-03 08:58:33 -07:00
|
|
|
csvString += pos.time?.formattedDate(format: dateFormatString) ?? "Unknown Age".localized
|
2022-07-15 15:01:42 -07:00
|
|
|
}
|
|
|
|
|
return csvString
|
|
|
|
|
}
|
2024-01-16 15:50:59 -08:00
|
|
|
|
|
|
|
|
func routeToCsvFile(locations: [LocationEntity]) -> String {
|
|
|
|
|
var csvString: String = ""
|
|
|
|
|
// Create Position Header
|
|
|
|
|
csvString = "Id, Latitude, Longitude, Altitude, Speed, Heading"
|
|
|
|
|
for loc in locations {
|
|
|
|
|
csvString += "\n"
|
|
|
|
|
csvString += String(loc.id)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String((loc.latitude ?? 0))
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(loc.longitude ?? 0)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(loc.altitude)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(loc.speed)
|
|
|
|
|
csvString += ", "
|
|
|
|
|
csvString += String(loc.heading)
|
|
|
|
|
}
|
|
|
|
|
return csvString
|
|
|
|
|
}
|