V 1.26.10 Add file export functionality for Mesh Activity Log

This commit is contained in:
Garth Vander Houwen 2021-10-23 10:27:10 -07:00
parent ba9eb388ce
commit 2ee000f84a
6 changed files with 77 additions and 21 deletions

View file

@ -22,6 +22,7 @@
DD4A91202708C66600501B7E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911F2708C66600501B7E /* Configuration.swift */; };
DD8169F9271F1A6100F4AB02 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* Logger.swift */; };
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; };
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; };
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AE626F6B38600ABCC23 /* Connect.swift */; };
DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEC26F858F900ABCC23 /* MeshData.swift */; };
DD836AEF26F85D8D00ABCC23 /* NodeInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEE26F85D8D00ABCC23 /* NodeInfoModel.swift */; };
@ -84,6 +85,7 @@
DD4A911F2708C66600501B7E /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = "<group>"; };
DD836AE626F6B38600ABCC23 /* Connect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connect.swift; sourceTree = "<group>"; };
DD836AEC26F858F900ABCC23 /* MeshData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshData.swift; sourceTree = "<group>"; };
DD836AEE26F85D8D00ABCC23 /* NodeInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoModel.swift; sourceTree = "<group>"; };
@ -170,6 +172,7 @@
DD4A911D2708C65400501B7E /* AppSettings.swift */,
DD4A911F2708C66600501B7E /* Configuration.swift */,
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */,
DD8169FE272476C700F4AB02 /* LogDocument.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -474,6 +477,7 @@
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
DD47E3DB26F3901B00029299 /* Channels.swift in Sources */,
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */,
DD23A51326FEF5D500D9B90C /* MessageData.swift in Sources */,
DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */,
@ -668,7 +672,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.26.9;
MARKETING_VERSION = 1.26.10;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -695,7 +699,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.26.9;
MARKETING_VERSION = 1.26.10;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;

View file

@ -147,8 +147,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
self.centralManager?.connect(peripheral)
// Use a timer to keep track of connecting peripherals, context to pass the radio name with the timer and the RunLoop to prevent
// the timer from running on the main UI thread
let context = ["name": "@\(peripheral.name ?? "Unknown")"]
self.timeoutTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(timeoutTimerFired), userInfo: context, repeats: true)
RunLoop.current.add(self.timeoutTimer!, forMode: .common)
}
// Disconnect Device function
@ -186,28 +189,32 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
// called when a peripheral is connected
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
// Invalidate and reset connection timer count, remove any connection errors
lastConnectionError = ""
self.timeoutTimer!.invalidate()
self.runCount = 0
peripheral.delegate = self
// Map the peripheral to the connectedNode and connectedPeripheral ObservedObjects
connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first
let deviceName = peripheral.name ?? ""
let peripheralLast4: String = String(deviceName.suffix(4))
connectedNode = meshData.nodes.first(where: { $0.user.id.contains(peripheralLast4) })
lastConnectedPeripheral = peripheral.identifier.uuidString
// Discover Services
peripheral.discoverServices([meshtasticServiceCBUUID])
if meshLoggingEnabled { Logger.log("BLE Connected: \(peripheral.name ?? "Unknown")") }
print("BLE Connected: \(peripheral.name ?? "Unknown")")
// Clear the "Available Radios" list
peripherals.removeAll()
}
// Disconnect Peripheral Event
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
{
peripheral.delegate = self
// Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake
self.startScanning()
self.connectedPeripheral = nil
@ -215,9 +222,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if let e = error {
// https://developer.apple.com/documentation/corebluetooth/cberror/code
let errorCode = (e as NSError).code
if errorCode == 6 { // The connection has timed out unexpectedly.
// unknown = 0,
if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly.
// Happens when device is manually reset / powered off
// We will try and re-connect to this device
@ -228,7 +237,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
self.connectTo(peripheral: peripheral)
}
}
else if errorCode == 7 { // The specified device has disconnected from us.
else if errorCode == 7 { //CBError.Code.peripheralDisconnected The specified device has disconnected from us.
// Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work.
lastConnectionError = e.localizedDescription
@ -259,6 +268,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
// Discover Services Event
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
peripheral.delegate = self
if let e = error {
print("Discover Services error \(e)")
@ -282,6 +292,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
// Discover Characteristics Event
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
peripheral.delegate = self
if let e = error {
print("Discover Characteristics error \(e)")
@ -328,6 +339,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
// Data Read / Update Characteristic Event
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
{
peripheral.delegate = self
if let e = error {
print("didUpdateValueFor Characteristic error \(e)")

View file

@ -38,7 +38,7 @@ struct Connect: View {
.textCase(nil)
}
Section(header: Text("Connected Device").font(.title)) {
Section(header: Text("Connected Radio").font(.title)) {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected {
HStack {
@ -126,7 +126,7 @@ struct Connect: View {
.textCase(nil)
if bleManager.peripherals.count > 0 {
Section(header: Text("Available Devices").font(.title)) {
Section(header: Text("Available Radios").font(.title)) {
ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.rssi > $1.rssi })) { peripheral in
HStack {
Image(systemName: "circle.fill")

View file

@ -154,6 +154,7 @@ struct Messages: View {
.focused($focusedField, equals: .messageText)
.multilineTextAlignment(.leading)
.frame(minHeight: bounds.size.height / 4, maxHeight: bounds.size.height / 4)
Text(typingMessage).opacity(0).padding(.all, 0)
@ -187,14 +188,9 @@ struct Messages: View {
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown"))
}
)
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") )
})
.onAppear {
messageData.load()

View file

@ -0,0 +1,25 @@
import SwiftUI
import UniformTypeIdentifiers
struct LogDocument: FileDocument{
static var readableContentTypes:[UTType] {[.plainText]}
var logFile: String
init(logFile: String){
self.logFile = logFile
}
init(configuration: ReadConfiguration) throws{
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else{
throw CocoaError(.fileReadCorruptFile)
}
logFile = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
return FileWrapper(regularFileWithContents: logFile.data(using: .utf8)!)
}
}

View file

@ -1,10 +1,13 @@
import SwiftUI
import Foundation
import UniformTypeIdentifiers
struct MeshLog: View {
let logFile = Logger.logFile
var text = ""
@State private var logs = [String]()
@State private var isExporting: Bool = false
@State private var document: LogDocument = LogDocument(logFile: "")
var body: some View {
@ -16,12 +19,29 @@ struct MeshLog: View {
logs.removeAll()
for try await log in url.lines {
logs.append(log)
document.logFile.append(log)
}
logs.reverse()
} catch {
// Stop adding logs when an error is thrown
}
}
.fileExporter(isPresented: $isExporting,
document: document,
contentType: UTType.plainText,
defaultFilename: "mesh-activity-log"
)
{
result in
if case .success = result {
print("Upload was ok")
}
else{
print("Something went wrong")
}
}
.textSelection(.enabled)
.font(.caption2)
@ -49,7 +69,7 @@ struct MeshLog: View {
Spacer()
Button(action: {
isExporting = true
}) {
Image(systemName: "arrow.down.circle.fill").imageScale(.large).foregroundColor(.gray)
Text("Download Log")
@ -59,7 +79,6 @@ struct MeshLog: View {
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
.hidden()
Spacer()