diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index f55457b7..849486b4 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -11,16 +11,16 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph @ObservedObject private var meshData : MeshData @ObservedObject private var messageData : MessageData - @Published private var centralManager: CBCentralManager! + private var centralManager: CBCentralManager! + @Published var connectedPeripheral: Peripheral! @Published var connectedNode: NodeInfoModel! - @Published var lastConnectedNode: String @Published var lastConnectionError: String @Published var isSwitchedOn = false @Published var peripherals = [Peripheral]() - @Published private var broadcastNodeId: UInt32 = 4294967295 - + private var broadcastNodeId: UInt32 = 4294967295 + /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! @@ -33,12 +33,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph /* init BLEManager */ override init() { + self.meshData = MeshData() self.messageData = MessageData() - self.lastConnectedNode = "" self.lastConnectionError = "" super.init() - centralManager = CBCentralManager(delegate: self, queue: nil) + //let options = [CBCentralManagerOptionRestoreIdentifierKey: "com.meshtastic.ble-central"] + centralManager = CBCentralManager(delegate: self, queue: nil)//, options: options) centralManager.delegate = self meshData.load() messageData.load() @@ -47,9 +48,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // called when bluetooth is enabled/disabled for the app func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { + isSwitchedOn = true } else { + isSwitchedOn = false } } @@ -58,6 +61,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph func startScanning() { if isSwitchedOn { + peripherals.removeAll() centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: nil) print("Scanning Started") @@ -69,11 +73,23 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if centralManager.isScanning { + self.centralManager.stopScan() print("Stopped Scanning") } } + // Connect to a specific peripheral + func connectTo(peripheral: CBPeripheral) { + stopScanning() + if connectedPeripheral != nil && connectedPeripheral.peripheral.state == CBPeripheralState.connected { + self.disconnectDevice() + } + //connectedPeripheral.peripheral = peripheral + self.centralManager?.connect(peripheral) + print("Connected to: \(peripheral.name ?? "Unknown")") + } + // Disconnect Device function func disconnectDevice(){ @@ -81,38 +97,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } else { + connectedNode = nil connectedPeripheral = nil } } - // Connect to a Device via UUID - func connectToDevice(id: String) { - - connectedPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == id }).first - - if connectedPeripheral != nil { - - lastConnectedNode = connectedPeripheral.peripheral.identifier.uuidString - self.centralManager?.connect(connectedPeripheral!.peripheral) - print("Connected to: \(connectedPeripheral.peripheral.name ?? "Unknown")") - } - else { - print("Connection failed connectedPeripheral is nil") - } - } - - // Connect to a specific peripheral - func connectTo(peripheral: CBPeripheral) { - - if connectedPeripheral.peripheral.state == CBPeripheralState.connected { - self.disconnectDevice() - } - connectedPeripheral.peripheral = peripheral - self.centralManager?.connect(connectedPeripheral!.peripheral) - print("Connected to: \(connectedPeripheral.peripheral.name ?? "Unknown")") - } - // Called each time a peripheral is discovered func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { @@ -134,9 +124,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { peripheral.delegate = self + connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first + peripheral.discoverServices([meshtasticServiceCBUUID]) print("Peripheral connected: " + peripheral.name!) - startScanning() } // Send Broadcast Message @@ -146,11 +137,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Return false if we are not properly connected to a device, handle retry logic in the view for now if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected || self.connectedNode == nil { - - if connectedPeripheral != nil && self.connectedNode == nil { - self.disconnectDevice() - // Lets disconnect and then reconnect a second later when the message retry happens - } + success = false } else if message.count < 1 { @@ -191,7 +178,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Disconnect Peripheral Event func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { - // Start a Scan so the disconnected peripheral is moved to the peripherals[] + // Start a Scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() if let e = error { @@ -199,35 +186,38 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let errorCode = (e as NSError).code if errorCode == 6 { // The connection has timed out unexpectedly. - // Happens when device is manually reset / powered off - connectedPeripheral = nil - connectedNode = nil + // Happens when device is manually reset / powered off + // 2 second delay for device to power back on + let _ = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in + + self.connectTo(peripheral: peripheral) + } } else if errorCode == 7 { // The specified device has disconnected from us. - // Check if the peripheral is still visible and then reconnect + // Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work. + // Check if the last connected peripheral is still visible and then reconnect connectedPeripheral = nil connectedNode = nil } else if errorCode == 14 { // Peer error that may require forgetting device in settings + + // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that lastConnectionError = (e as NSError).description - // Check if the peripheral is still visible and then reconnect connectedPeripheral = nil connectedNode = nil } } else { + // Disconnected without error which indicates user intent to disconnect print("Central disconnected! (no error)") connectedPeripheral = nil connectedNode = nil } - - print("Peripheral disconnected: " + peripheral.name!) - } // Discover Services Event @@ -307,9 +297,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph { case FROMNUM_UUID: peripheral.readValue(for: FROMNUM_characteristic) - //let byteArrayFromData: [UInt8] = [UInt8](characteristic.value!) - //let stringFromByteArray = String(data: Data(_: byteArrayFromData), encoding: .ascii) - //print(stringFromByteArray) + let byteArrayFromData: [UInt8] = [UInt8](characteristic.value!) + let stringFromByteArray = String(data: Data(_: byteArrayFromData), encoding: .ascii) + print("string array data \(stringFromByteArray)") //print(characteristic.value?. ?? "no value") @@ -347,9 +337,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph connectedNode = meshData.nodes.first(where: {$0.num == myInfoModel.id}) } - // Since the data is from the device itself we save all myInfo objects since they are always the most update - if connectedNode != nil && connectedNode.myInfo == nil { + // Since the data is from the device itself we save all myInfo objects since they are always the most up to date + if connectedNode != nil { connectedNode.myInfo = myInfoModel + //connectedNode.update(from: connectedNode.data) let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.myInfo.myNodeNum }) meshData.nodes.remove(at: nodeIndex!) meshData.nodes.append(connectedNode) @@ -367,7 +358,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Found a matching node lets update it let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num }) - if nodeMatch?.lastHeard ?? 0 < decodedInfo.nodeInfo.lastHeard { + if connectedPeripheral != nil && connectedPeripheral.myInfo?.id == nodeMatch?.num { + connectedNode = nodeMatch + } + + if nodeMatch?.lastHeard ?? 0 < decodedInfo.nodeInfo.lastHeard && nodeMatch?.user != nil && nodeMatch?.user.longName.count ?? 0 > 0 { // The data coming from the device is newer let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.nodeInfo.num }) @@ -380,7 +375,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return } } - + // Set the connected node if the nodeInfo is for the connected node. + if connectedPeripheral != nil && connectedPeripheral.myInfo?.id == decodedInfo.nodeInfo.num { + + let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num }) + if nodeMatch != nil { + connectedNode = nodeMatch + } + } meshData.nodes.append( NodeInfoModel( num: decodedInfo.nodeInfo.num, @@ -522,4 +524,5 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } peripheral.readValue(for: FROMRADIO_characteristic) } + } diff --git a/MeshtasticClient/Model/PeripheralModel.swift b/MeshtasticClient/Model/PeripheralModel.swift index c96b2014..5f04b3f5 100644 --- a/MeshtasticClient/Model/PeripheralModel.swift +++ b/MeshtasticClient/Model/PeripheralModel.swift @@ -17,3 +17,4 @@ final class Peripheral: Identifiable { self.myInfo = myInfo } } + diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index 86e7cbed..a1311e5d 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -51,19 +51,19 @@ struct Connect: View { Text((bleManager.connectedPeripheral!.peripheral.name != nil) ? bleManager.connectedPeripheral!.peripheral.name! : "Unknown").font(.title2) } } - .padding() .swipeActions { - Button { + + Button(role: .destructive) { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - bleManager.disconnectDevice() + bleManager.disconnectDevice() } } label: { - Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") + Label("Delete", systemImage: "antenna.radiowaves.left.and.right.slash") } - .tint(.red) } + .padding() } else { HStack{ @@ -90,7 +90,7 @@ struct Connect: View { { self.bleManager.disconnectDevice() } - self.bleManager.connectToDevice(id: peripheral.id) + self.bleManager.connectTo(peripheral: peripheral.peripheral) }) { Text(peripheral.name).font(.title3) } @@ -140,16 +140,14 @@ struct Connect: View { .navigationTitle("Bluetooth Radios") .navigationBarItems(trailing: - VStack(alignment: .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") ) - + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ) } ) - }.navigationViewStyle(StackNavigationViewStyle()) - .onAppear{ - bleManager.startScanning() } + .navigationViewStyle(StackNavigationViewStyle()) + .onAppear(perform: { bleManager.startScanning() } ) } } diff --git a/MeshtasticClient/Views/Messages/Channels.swift b/MeshtasticClient/Views/Messages/Channels.swift index 612c8825..3ddccb7e 100644 --- a/MeshtasticClient/Views/Messages/Channels.swift +++ b/MeshtasticClient/Views/Messages/Channels.swift @@ -3,6 +3,9 @@ import SwiftUI import CoreBluetooth struct Channels: View { + // Message Data and Bluetooth + @EnvironmentObject var messageData: MessageData + @EnvironmentObject var bleManager: BLEManager var body: some View { NavigationView { @@ -31,6 +34,7 @@ struct Channels: View { } .navigationTitle("Channels") } + .navigationViewStyle(StackNavigationViewStyle()) } } diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index b37c3355..6efdc5b2 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -91,18 +91,15 @@ struct Messages: View { typingMessage = "" } else { - if bleManager.lastConnectedNode.count > 10 { - if bleManager.peripherals.contains(where: { $0.id == bleManager.lastConnectedNode }) { - bleManager.connectToDevice(id: bleManager.lastConnectedNode) - let timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (timer) in - - if bleManager.sendMessage(message: typingMessage) { - typingMessage = "" - } + + if bleManager.connectedPeripheral != nil { + let timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (timer) in + + if bleManager.sendMessage(message: typingMessage) { + typingMessage = "" } } } - } } ) {