From b1303832f7fba167a22c8ff0dcd1ea04bbf93f16 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 5 Oct 2021 09:33:10 -0700 Subject: [PATCH] Version 1. --- Meshtastic Client.xcodeproj/project.pbxproj | 10 +- MeshtasticClient/Helpers/BLEHelper.swift | 32 -- MeshtasticClient/Helpers/BLEManager.swift | 291 +++++++++--------- MeshtasticClient/Resources/packets.json | 14 - .../Views/Bluetooth/Connect.swift | 3 + .../Views/Messages/Messages.swift | 7 + MeshtasticClient/Views/Nodes/NodeList.swift | 10 +- MeshtasticClient/Views/Nodes/NodeRow.swift | 9 +- 8 files changed, 177 insertions(+), 199 deletions(-) delete mode 100644 MeshtasticClient/Helpers/BLEHelper.swift delete mode 100644 MeshtasticClient/Resources/packets.json diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 79067a9b..19155ef7 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -18,7 +18,6 @@ DD47E3DB26F3901B00029299 /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DA26F3901A00029299 /* Channels.swift */; }; DD47E3DD26F390A000029299 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DC26F390A000029299 /* Messages.swift */; }; DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */; }; - DD4A911B2708303E00501B7E /* BLEHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911A2708303E00501B7E /* BLEHelper.swift */; }; DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; }; DD4A91202708C66600501B7E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911F2708C66600501B7E /* Configuration.swift */; }; DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AE626F6B38600ABCC23 /* Connect.swift */; }; @@ -78,7 +77,6 @@ DD47E3DA26F3901A00029299 /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DD47E3DC26F390A000029299 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoModel.swift; sourceTree = ""; }; - DD4A911A2708303E00501B7E /* BLEHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEHelper.swift; sourceTree = ""; }; DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; DD4A911F2708C66600501B7E /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; DD836AE626F6B38600ABCC23 /* Connect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connect.swift; sourceTree = ""; }; @@ -113,7 +111,6 @@ DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = ""; }; DDF924C526FA2375009FE055 /* MessageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageModel.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; - DDF924CB26FCC916009FE055 /* packets.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = packets.json; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -286,7 +283,6 @@ DDC2E18926CE24F70042C5E4 /* Resources */ = { isa = PBXGroup; children = ( - DDF924CB26FCC916009FE055 /* packets.json */, ); path = Resources; sourceTree = ""; @@ -318,7 +314,6 @@ DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */, DDAF8C6D26ED19040058C060 /* Extensions.swift */, DD47E3D126F1210600029299 /* HelperFunctions.swift */, - DD4A911A2708303E00501B7E /* BLEHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -470,7 +465,6 @@ DD47E3DB26F3901B00029299 /* Channels.swift in Sources */, DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */, DD23A51326FEF5D500D9B90C /* MessageData.swift in Sources */, - DD4A911B2708303E00501B7E /* BLEHelper.swift in Sources */, DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */, DDAF8C6B26ED0DD80058C060 /* environmental_measurement.pb.swift in Sources */, DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */, @@ -661,7 +655,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.20; + MARKETING_VERSION = 1.21; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = NO; @@ -688,7 +682,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.20; + MARKETING_VERSION = 1.21; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = NO; diff --git a/MeshtasticClient/Helpers/BLEHelper.swift b/MeshtasticClient/Helpers/BLEHelper.swift deleted file mode 100644 index 70a2c263..00000000 --- a/MeshtasticClient/Helpers/BLEHelper.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation -import CoreData -import CoreBluetooth -import SwiftUI - -//--------------------------------------------------------------------------------------- -// Meshtastic BLE Device Manager -//--------------------------------------------------------------------------------------- -class BLEHelper: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { - - var centralManager: CBCentralManager! - @Published var isSwitchedOn = false - @Published var peripherals = [Peripheral]() - - override init() { - - super.init() - centralManager = CBCentralManager(delegate: self, queue: nil) - centralManager.delegate = self - } - - // Check for Bluetooth Connectivity - func centralManagerDidUpdateState(_ central: CBCentralManager) { - if central.state == .poweredOn { - isSwitchedOn = true - } - else { - isSwitchedOn = false - } - } - -} diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 8d42be26..357cba05 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -8,23 +8,16 @@ import SwiftUI //--------------------------------------------------------------------------------------- class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { - // Data @ObservedObject private var meshData : MeshData @ObservedObject private var messageData : MessageData private var centralManager: CBCentralManager! @Published var connectedPeripheral: Peripheral! - //@Published var peripheralArray = [CBPeripheral]() - //@Published var connectedNodeInfo: Peripheral! @Published var connectedNode: NodeInfoModel! @Published var lastConnectedNode: String - - //private var rssiArray = [NSNumber]() - private var broadcastNodeId: UInt32 = 4294967295 - @Published var isSwitchedOn = false @Published var peripherals = [Peripheral]() - + private var broadcastNodeId: UInt32 = 4294967295 /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! @@ -48,7 +41,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph messageData.load() } - /* Check for Bluetooth Connectivity */ + // called when bluetooth is enabled/disabled for the app func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { isSwitchedOn = true @@ -58,42 +51,98 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } - /* Scan for nearby BLE devices using the Meshtastic BLE service ID */ + // Scan for nearby BLE devices using the Meshtastic BLE service ID func startScanning() { - peripherals.removeAll() - centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID]) - print("Scanning Started") + if isSwitchedOn { + peripherals.removeAll() + centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: nil) + print("Scanning Started") + } } - /*Stop Scanning For BLE Devices */ + // Stop Scanning For BLE Devices func stopScanning() { - self.centralManager.stopScan() - print("Stopped Scanning") + if centralManager.isScanning { + + self.centralManager.stopScan() + print("Stopped Scanning") + } } - /* Connect to a Device via UUID */ - func connectToDevice(id: String) { - connectedPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == id }).first - lastConnectedNode = connectedPeripheral.peripheral.identifier.uuidString - self.centralManager?.connect(connectedPeripheral!.peripheral) - } - - /* Disconnect Device function */ + // Disconnect Device function func disconnectDevice(){ + if connectedPeripheral != nil && connectedPeripheral.peripheral.state == CBPeripheralState.connected { self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } } - /* Send Broadcast Message */ + // 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 { + 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) { + + peripheral.delegate = self + + var peripheralName: String = peripheral.name ?? "Unknown" + + if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String { + peripheralName = name + } + + let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, name: peripheralName, rssi: RSSI.intValue, peripheral: peripheral, myInfo: nil) + + peripherals.append(newPeripheral) + print("Adding peripheral: \(peripheralName)"); + } + + // called when a peripheral is connected + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + + peripheral.delegate = self + peripheral.discoverServices([meshtasticServiceCBUUID]) + print("Peripheral connected: " + peripheral.name!) + startScanning() + } + + // Send Broadcast Message public func sendMessage(message: String) -> Bool { var success = true if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected { success = false - connectToDevice(id: lastConnectedNode) + if lastConnectedNode.count > 10 { + connectToDevice(id: lastConnectedNode) + //Thread.sleep(forTimeInterval: 3) + } + } else { @@ -129,37 +178,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } return success } - - /* Discover Peripheral Event */ - func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { - - print("Adding peripheral: " + ((peripheral.name != nil) ? peripheral.name! : "(null)")); - - var peripheralName: String! - peripheralName = peripheral.name - if peripheral.name == nil { - if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String { - peripheralName = name - } - else { - peripheralName = "Unknown" - } - } - let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, name: peripheralName, rssi: RSSI.intValue, peripheral: peripheral, myInfo: nil) - //print(newPeripheral) - peripherals.append(newPeripheral) - } - - /* Connect Peripheral Event */ - func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { - - print("Peripheral connected: " + peripheral.name!) - peripheral.delegate = self - peripheral.discoverServices(nil) - } - - /* Disconnect Peripheral Event */ + // Disconnect Peripheral Event func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { @@ -178,7 +198,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.startScanning() } - /* Discover Services Event */ + // Discover Services Event func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard let services = peripheral.services else { return } @@ -190,13 +210,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if (service.uuid == meshtasticServiceCBUUID) { print ("Meshtastic service OK") - - peripheral.discoverCharacteristics(nil, for: service) + peripheral.discoverCharacteristics([TORADIO_UUID, FROMRADIO_UUID, FROMNUM_UUID], for: service) } } } - /* Discover Characteristics Event */ + // Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristics = service.characteristics else { return } @@ -233,35 +252,38 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } - /* Data Read / Update Characteristic Event */ + // Data Read / Update Characteristic Event func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { switch characteristic.uuid { case FROMNUM_UUID: peripheral.readValue(for: FROMNUM_characteristic) - peripheral.setNotifyValue(true, for: characteristic) - print(characteristic.value ?? "no value") + //let byteArrayFromData: [UInt8] = [UInt8](characteristic.value!) + //let stringFromByteArray = String(data: Data(_: byteArrayFromData), encoding: .ascii) + //print(stringFromByteArray) + //print(characteristic.value?. ?? "no value") + case FROMRADIO_UUID: if (characteristic.value == nil || characteristic.value!.isEmpty) { return } - print(characteristic.value ?? "no value") + print(characteristic.value ?? "no value") // print(characteristic.value?.hexDescription ?? "no value") var decodedInfo = FromRadio() decodedInfo = try! FromRadio(serializedData: characteristic.value!) - print("Print DecodedInfo") - print(decodedInfo) + //print("Print DecodedInfo") + // print(decodedInfo) if decodedInfo.myInfo.myNodeNum != 0 { print("Save a myInfo") do { - print(try decodedInfo.myInfo.jsonString()) + // print(try decodedInfo.myInfo.jsonString()) // Create a MyInfoModel let myInfoModel = MyInfoModel( @@ -334,7 +356,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph ) meshData.save() - print(try decodedInfo.nodeInfo.jsonString()) + // print(try decodedInfo.nodeInfo.jsonString()) } catch { fatalError("Failed to decode json") } @@ -342,80 +364,69 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if decodedInfo.packet.id != 0 { - //let byteArray = [UInt8](characteristic.value!) - print("Try and decode packet") - - do { + print("Handle a Packet") + do { - if decodedInfo.packet.decoded.portnum == PortNum.textMessageApp { - - if let messageText = String(bytes: decodedInfo.packet.decoded.payload, encoding: .utf8) { - print(messageText) - print(try decodedInfo.packet.jsonString()) - - let broadcastNodeId: UInt32 = 4294967295 - - let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) - - var toUserLongName: String = "Broadcast" - var toUserShortName: String = "BC" - - if decodedInfo.packet.to != broadcastNodeId { + if decodedInfo.packet.decoded.portnum == PortNum.textMessageApp { + + if let messageText = String(bytes: decodedInfo.packet.decoded.payload, encoding: .utf8) { + print("Message Text: \(messageText)") + + let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) + + var toUserLongName: String = "Broadcast" + var toUserShortName: String = "BC" + + if decodedInfo.packet.to != broadcastNodeId { + + let toUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) + toUserLongName = toUser?.user.longName ?? "Unknown" + toUserShortName = toUser?.user.shortName ?? "???" + } + + messageData.messages.append( + MessageModel(messageId: decodedInfo.packet.id, messageTimeStamp: decodedInfo.packet.rxTime, fromUserId: decodedInfo.packet.from, toUserId: decodedInfo.packet.to, fromUserLongName: fromUser?.user.longName ?? "Unknown", toUserLongName: toUserLongName, fromUserShortName: fromUser?.user.shortName ?? "???", toUserShortName: toUserShortName, receivedACK: decodedInfo.packet.decoded.wantResponse, messagePayload: messageText, direction: "IN")) + messageData.save() + + } else { + print("not a valid UTF-8 sequence") + } - let toUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) - toUserLongName = toUser?.user.longName ?? "Unknown" - toUserShortName = toUser?.user.shortName ?? "???" } + else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp { + + var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from}) + updatedNode!.snr = decodedInfo.packet.rxSnr + updatedNode!.lastHeard = decodedInfo.packet.rxTime + // updatedNode!.user.longName = "Node Info updated longname" + // updatedNode!.update(from: updatedNode!.data) + + let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from }) + meshData.nodes.remove(at: nodeIndex!) + meshData.nodes.append(updatedNode!) + meshData.save() + print("Updated NodeInfo SNR and Time from Packet For: \(updatedNode!.user.longName)") + } + else if decodedInfo.packet.decoded.portnum == PortNum.positionApp { - messageData.messages.append( - MessageModel(messageId: decodedInfo.packet.id, messageTimeStamp: decodedInfo.packet.rxTime, fromUserId: decodedInfo.packet.from, toUserId: decodedInfo.packet.to, fromUserLongName: fromUser?.user.longName ?? "Unknown", toUserLongName: toUserLongName, fromUserShortName: fromUser?.user.shortName ?? "???", toUserShortName: toUserShortName, receivedACK: decodedInfo.packet.decoded.wantResponse, messagePayload: messageText, direction: "IN")) - messageData.save() - - } else { - print("not a valid UTF-8 sequence") - } - - } - else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp { - - var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from}) - updatedNode!.snr = decodedInfo.packet.rxSnr - updatedNode!.lastHeard = decodedInfo.packet.rxTime - updatedNode!.update(from: updatedNode!.data) - - if let nodeInfoPayload = String(bytes: decodedInfo.packet.decoded.payload, encoding: .ascii) { - print(nodeInfoPayload) - } else { - print("not a valid UTF-8 sequence") - print(try decodedInfo.packet.jsonString()) - } - - } - else if decodedInfo.packet.decoded.portnum == PortNum.positionApp { - - //Set time and snr from nodeinfo - if let nodeInfoPayload = String(bytes: decodedInfo.packet.decoded.payload, encoding: .unicode) { - print(nodeInfoPayload) - } else { - print("not a valid UTF-8 sequence") - print(try decodedInfo.packet.jsonString()) - } - } - else if decodedInfo.packet.decoded.portnum == PortNum.adminApp { - - //Set time and snr from nodeinfo - if let nodeInfoPayload = String(bytes: decodedInfo.packet.decoded.payload, encoding: .unicode) { - print(nodeInfoPayload) - } else { - print("not a valid UTF-8 sequence") - print(try decodedInfo.packet.jsonString()) - } - } - else - { - print("Save a packet") - print(try decodedInfo.packet.jsonString()) - } + print("Postion Payload") + print(try decodedInfo.packet.jsonString()) + } + else if decodedInfo.packet.decoded.portnum == PortNum.adminApp { + + print("Admin App Packet") + print(try decodedInfo.packet.jsonString()) + } + else if decodedInfo.packet.decoded.portnum == PortNum.routingApp { + + print("Routing App Packet") + //print(try decodedInfo.packet.jsonString()) + } + else + { + print("Other App Packet") + print(try decodedInfo.packet.jsonString()) + } } catch { fatalError("Failed to decode json") @@ -423,15 +434,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } if decodedInfo.configCompleteID != 0 { - print(decodedInfo) + print("Config Complete: \(decodedInfo)") } default: print("Unhandled Characteristic UUID: \(characteristic.uuid)") } - peripheral.readValue(for: FROMRADIO_characteristic) } } - diff --git a/MeshtasticClient/Resources/packets.json b/MeshtasticClient/Resources/packets.json deleted file mode 100644 index a4a6da5a..00000000 --- a/MeshtasticClient/Resources/packets.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "from":4064715620, - "to":4294967295, - "decoded":{ - "portnum":"TEXT_MESSAGE_APP", - "payload":"YmFsbHM=" - - }, - "id":3773493287, - "rxTime":1632407404, - "rxSnr":10.75, - "hopLimit":3} -] diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index 67816f0e..cf65add1 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -142,6 +142,9 @@ struct Connect: View { } ) }.navigationViewStyle(StackNavigationViewStyle()) + .onAppear{ + bleManager.startScanning() + } } } diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index 439fe8bd..c59d5538 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -90,6 +90,13 @@ struct Messages: View { if bleManager.sendMessage(message: typingMessage) { typingMessage = "" } + else { + let timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (timer) in + if bleManager.sendMessage(message: typingMessage) { + typingMessage = "" + } + } + } } ) { Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue) diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift index 5a708067..b7be8bce 100644 --- a/MeshtasticClient/Views/Nodes/NodeList.swift +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -45,7 +45,15 @@ struct NodeList: View { } ForEach(filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard })) { node in NavigationLink(destination: NodeDetail(node: node)) { - NodeRow(node: node, index : 0) + + if(bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.myInfo != nil) { + + let connected: Bool = (bleManager.connectedPeripheral.myInfo!.id == node.id) + NodeRow(node: node, connected: connected) + } + else { + NodeRow(node: node, connected: false) + } } .swipeActions { diff --git a/MeshtasticClient/Views/Nodes/NodeRow.swift b/MeshtasticClient/Views/Nodes/NodeRow.swift index 7ec04e0b..7b0bb978 100644 --- a/MeshtasticClient/Views/Nodes/NodeRow.swift +++ b/MeshtasticClient/Views/Nodes/NodeRow.swift @@ -2,7 +2,7 @@ import SwiftUI struct NodeRow: View { var node: NodeInfoModel - var index: Int + var connected: Bool var body: some View { VStack (alignment: .leading) { @@ -18,7 +18,10 @@ struct NodeRow: View { Image(systemName: "timer").font(.headline).foregroundColor(.blue).symbolRenderingMode(.hierarchical) - if node.lastHeard > 0 { + if connected { + Text("Currently Connected").font(.subheadline).foregroundColor(Color.accentColor) + } + else if node.lastHeard > 0 { let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard)) Text("Last Heard: \(lastHeard, style: .relative) ago").font(.subheadline).foregroundColor(.gray) } @@ -35,7 +38,7 @@ struct NodeRow_Previews: PreviewProvider { static var previews: some View { Group { - NodeRow(node: nodes[0], index: 0) + NodeRow(node: nodes[0], connected: true) } .previewLayout(.fixed(width: 300, height: 70)) }