mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
V 1.26.10 Add file export functionality for Mesh Activity Log
This commit is contained in:
parent
ba9eb388ce
commit
2ee000f84a
6 changed files with 77 additions and 21 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
25
MeshtasticClient/Views/Settings/LogDocument.swift
Normal file
25
MeshtasticClient/Views/Settings/LogDocument.swift
Normal 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)!)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue