diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 6a0a9262..fc4d26c5 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -668,13 +668,13 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.26.4; + MARKETING_VERSION = 1.26.5; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,6"; }; name = Debug; }; @@ -695,13 +695,13 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.26.4; + MARKETING_VERSION = 1.26.5; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,6"; }; name = Release; }; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 48b290f2..ffbce4f3 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -32,6 +32,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph @Published var isSwitchedOn = false @Published var peripherals = [Peripheral]() + var isDisconnectedByUser = false + var timeoutTimer: Timer? + private var meshLoggingEnabled: Bool = true private var broadcastNodeId: UInt32 = 4294967295 @@ -78,7 +81,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if isSwitchedOn { - peripherals.removeAll() centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: nil) print("Scanning Started") } @@ -93,15 +95,35 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("Stopped Scanning") } } + + /// The action after the timeout-timer has fired + /// + /// - Parameters: + /// - timer: The time that fired the event + /// + @objc func timeoutTimerFired(timer: Timer) + { + timer.invalidate() + lastConnectionError = "BLE Connection timed out" //radio \(connectedPeripheral.peripheral.name ?? "Unknown") + if connectedPeripheral != nil { + self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) + connectedNode = nil + connectedPeripheral = nil + } + print("BLE-Timeout-Timer fired!") + Logger.log("BLE-Timeout-Timer fired!") + self.startScanning() + } // Connect to a specific peripheral func connectTo(peripheral: CBPeripheral) { stopScanning() - if connectedPeripheral != nil && connectedPeripheral.peripheral.state == CBPeripheralState.connected { + if self.connectedPeripheral != nil && self.connectedPeripheral.peripheral.state == CBPeripheralState.connected { self.disconnectDevice() } + self.timeoutTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(timeoutTimerFired), userInfo: nil, repeats: false) self.centralManager?.connect(peripheral) if meshLoggingEnabled { Logger.log("BLE Connecting: \(peripheral.name ?? "Unknown")") } print("BLE Connecting: \(peripheral.name ?? "Unknown")") @@ -125,26 +147,38 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, name: peripheralName, rssi: RSSI.intValue, peripheral: peripheral, myInfo: nil) - peripherals.append(newPeripheral) - print("Adding peripheral: \(peripheralName)"); + let peripheralIndex = peripherals.firstIndex(where: { $0.id == newPeripheral.id }) + + if peripheralIndex != nil { + peripherals.remove(at: peripheralIndex!) + peripherals.append(newPeripheral) + print("Updating peripheral: \(peripheralName)"); + } + else { + + peripherals.append(newPeripheral) + print("Adding peripheral: \(peripheralName)"); + } } // called when a peripheral is connected func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { - stopScanning() + lastConnectionError = "" + timeoutTimer!.invalidate() + peripheral.delegate = self connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first let deviceName = peripheral.name ?? "" let peripheralLast4: String = String(deviceName.suffix(4)) - print(peripheralLast4) connectedNode = meshData.nodes.first(where: { $0.user.id.contains(peripheralLast4) }) lastConnectedPeripheral = peripheral.identifier.uuidString peripheral.discoverServices([meshtasticServiceCBUUID]) if meshLoggingEnabled { Logger.log("BLE Connected: \(peripheral.name ?? "Unknown")") } print("BLE Connected: \(peripheral.name ?? "Unknown")") + stopScanning() peripherals.removeAll() } @@ -158,16 +192,20 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let errorCode = (e as NSError).code - if errorCode == 6 { // The connection has timed out unexpectedly. + if errorCode == 6 { + + // Error Code 6: The connection has timed out unexpectedly. + // Happens when device is manually reset / powered off + lastConnectionError = " \(e.localizedDescription) Will automatically attempt to reconnect to the preferred radio if it reappears quickly." + self.connectedNode = nil + self.connectedPeripheral = nil + self.connectTo(peripheral: peripheral) - // Happens when device is manually reset / powered off // 2 second delay for device to power back on - let _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { (timer) in + //let _ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (timer) in - //self.connectedPeripheral = nil - self.connectedNode = nil - self.connectTo(peripheral: peripheral) - } + + // } } else if errorCode == 7 { // The specified device has disconnected from us. @@ -175,6 +213,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Check if the last connected peripheral is still visible and then reconnect connectedPeripheral = nil connectedNode = nil + lastConnectionError = e.localizedDescription } else if errorCode == 14 { // Peer error that may require forgetting device in settings @@ -182,7 +221,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that connectedPeripheral = nil connectedNode = nil - lastConnectionError = (e as NSError).description + lastConnectionError = e.localizedDescription } print("Central disconnected because \(e)") if meshLoggingEnabled { Logger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } diff --git a/MeshtasticClient/MeshtasticClientApp.swift b/MeshtasticClient/MeshtasticClientApp.swift index 3c8d2753..23310165 100644 --- a/MeshtasticClient/MeshtasticClientApp.swift +++ b/MeshtasticClient/MeshtasticClientApp.swift @@ -13,6 +13,7 @@ struct MeshtasticClientApp: App { @ObservedObject private var meshData: MeshData = MeshData() @ObservedObject private var messageData: MessageData = MessageData() @ObservedObject private var bleManager: BLEManager = BLEManager() + @ObservedObject private var userSettings: UserSettings = UserSettings() var body: some Scene { WindowGroup { @@ -20,6 +21,7 @@ struct MeshtasticClientApp: App { .environmentObject(meshData) .environmentObject(messageData) .environmentObject(bleManager) + .environmentObject(userSettings) .onAppear{ meshData.load() messageData.load() diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index b3930589..1702641b 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -16,9 +16,10 @@ import CoreBluetooth struct Connect: View { @EnvironmentObject var meshData: MeshData - @EnvironmentObject var bleManager: BLEManager - @ObservedObject var userSettings = UserSettings() + + @EnvironmentObject var userSettings: UserSettings + @State var isPreferredRadio: Bool = false @@ -30,7 +31,17 @@ struct Connect: View { if bleManager.isSwitchedOn { List { + if bleManager.lastConnectionError.count > 0 { + + Section(header: Text("Connection Error").font(.title)) { + + Text(bleManager.lastConnectionError).font(.title2).foregroundColor(.red) + } + .textCase(nil) + } + Section(header: Text("Connected Device").font(.title)) { + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { HStack { @@ -64,19 +75,20 @@ struct Connect: View { .labelsHidden() .onChange(of: isPreferredRadio) { value in if value { + if bleManager.connectedNode != nil { let deviceName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")" - UserDefaults.standard.set(deviceName, forKey: "preferredPeripheralName") - //userSettings.preferredPeripheralName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")" + + userSettings.preferredPeripheralName = deviceName } else { - UserDefaults.standard.set(bleManager.connectedPeripheral.peripheral.name ?? "Unknown Device", forKey: "preferredPeripheralName") - //userSettings.preferredPeripheralName = + userSettings.preferredPeripheralName = bleManager.connectedPeripheral.peripheral.name ?? "Unknown Device" } - UserDefaults.standard.set(bleManager.connectedPeripheral!.peripheral.identifier.uuidString, forKey: "preferredPeripheralId") + + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString } else { - UserDefaults.standard.set("", forKey: "preferredPeripheralId") - UserDefaults.standard.set("", forKey: "preferredPeripheralName") + userSettings.preferredPeripheralId = "" + userSettings.preferredPeripheralName = "" } } } @@ -107,7 +119,8 @@ struct Connect: View { .padding() } - }.textCase(nil) + } + .textCase(nil) Section(header: Text("Available Devices").font(.title)) { ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.rssi > $1.rssi })) { peripheral in diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift index 62b412ea..4888c291 100644 --- a/MeshtasticClient/Views/Nodes/NodeList.swift +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -13,6 +13,8 @@ import SwiftUI struct NodeList: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var meshData: MeshData + + @State private var selection: String? = nil @State private var showLocationOnly = false @@ -47,7 +49,11 @@ struct NodeList: View { Text("Nodes with location only") } ForEach(filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard })) { node in - NavigationLink(destination: NodeDetail(node: node)) { + + + let index = filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard }).firstIndex(where: { $0.id == node.id }) + + NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) { if(bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.myInfo != nil) { @@ -75,6 +81,15 @@ struct NodeList: View { } } .navigationTitle("All Nodes") + .onAppear( + perform: { + if UIDevice.current.userInterfaceIdiom == .pad { + if meshData.nodes.count > 0 { + selection = "0" + } + } + } + ) } .ignoresSafeArea(.all, edges: [.leading, .trailing]) .navigationViewStyle(DoubleColumnNavigationViewStyle()) diff --git a/MeshtasticClient/Views/Settings/AppSettings.swift b/MeshtasticClient/Views/Settings/AppSettings.swift index f33d6b2a..9d349af8 100644 --- a/MeshtasticClient/Views/Settings/AppSettings.swift +++ b/MeshtasticClient/Views/Settings/AppSettings.swift @@ -74,9 +74,10 @@ class UserSettings: ObservableObject { struct AppSettings: View { - @State private var preferredDeviceConnected = false @EnvironmentObject var bleManager: BLEManager - @ObservedObject var userSettings = UserSettings() + @EnvironmentObject var userSettings: UserSettings + + @State private var preferredDeviceConnected = false var perferredPeripheral: String { UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? ""