From 693aec1fd3f875e5202e931ea2b69e96920672f5 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 Sep 2021 21:50:54 -0700 Subject: [PATCH] Complete BLE Scanning and connection --- MeshtasticClient/Views/ContentView.swift | 24 +- .../Views/Devices/DeviceBLE.swift | 45 +++- .../Views/Helpers/BLEManager.swift | 213 ++++++++++++++---- 3 files changed, 219 insertions(+), 63 deletions(-) diff --git a/MeshtasticClient/Views/ContentView.swift b/MeshtasticClient/Views/ContentView.swift index eff5d3de..7d322033 100644 --- a/MeshtasticClient/Views/ContentView.swift +++ b/MeshtasticClient/Views/ContentView.swift @@ -5,7 +5,7 @@ Abstract: Default App View import SwiftUI struct ContentView: View { - @State private var selection: Tab = .devices + @State private var selection: Tab = .ble enum Tab { case messages @@ -18,28 +18,26 @@ struct ContentView: View { var body: some View { TabView(selection: $selection) { - - DeviceHome() - .tabItem { - Label("Devices", systemImage: "flipphone") - } - .tag(Tab.devices) - DeviceMap() - .tabItem { - Label("Mesh Map", systemImage: "map") - } - .tag(Tab.map) Messages() .tabItem { Label("Messages", systemImage: "message") } .tag(Tab.messages) + DeviceMap() + .tabItem { + Label("Mesh Map", systemImage: "map") + } + .tag(Tab.map) + DeviceHome() + .tabItem { + Label("Devices", systemImage: "flipphone") + } + .tag(Tab.devices) DeviceBLE() .tabItem { Label("Bluetooth", systemImage: "dot.radiowaves.left.and.right") } .tag(Tab.ble) - } } } diff --git a/MeshtasticClient/Views/Devices/DeviceBLE.swift b/MeshtasticClient/Views/Devices/DeviceBLE.swift index 87aea3ef..d3baaaf7 100644 --- a/MeshtasticClient/Views/Devices/DeviceBLE.swift +++ b/MeshtasticClient/Views/Devices/DeviceBLE.swift @@ -24,17 +24,40 @@ struct DeviceBLE: View { NavigationView { VStack { - - List(bleManager.peripherals) { peripheral in - HStack { - Text(peripheral.name) - Spacer() - Text(String(peripheral.rssi) + " dB") - } - }.frame(height: 300) - + List { + Section(header: Text("Connected Device")) { + if(bleManager.connectedPeripheral != nil){ + HStack{ + Image(systemName: "dot.radiowaves.left.and.right").imageScale(.medium).foregroundColor(.green) + Text(bleManager.connectedPeripheral.name!) + Spacer() + // print(bleManager.meshtasticPeripheral) + } + } + + }.textCase(nil) + Section(header: Text("Other Meshtastic Devices")) { + ForEach(bleManager.peripherals.sorted(by: { $0.rssi > $1.rssi })) { peripheral in + HStack { + + Image(systemName: "circle.fill").imageScale(.medium).foregroundColor(.gray) + + Button(action: { + self.bleManager.stopScanning() + self.bleManager.disconnectDevice() + self.bleManager.connectToDevice(id: peripheral.id) + }) { + Text(peripheral.name) + } + Spacer() + Text(String(peripheral.rssi) + " dB") + } + } + }.textCase(nil) + } + // Image(systemName: "dot.radiowaves.left.and.right").imageScale(.medium).foregroundColor(.green)//.rotationEffect(Angle(degrees: 90)) + Spacer() - HStack { VStack (spacing: 10) { Button(action: { @@ -60,7 +83,7 @@ struct DeviceBLE: View { Button(action: { self.bleManager.startScanning() }) { - Image(systemName: "arrow.clockwise.circle.fill").imageScale(.large) + Image(systemName: "arrow.clockwise.circle").imageScale(.large) }}, trailing: HStack { if bleManager.isSwitchedOn { diff --git a/MeshtasticClient/Views/Helpers/BLEManager.swift b/MeshtasticClient/Views/Helpers/BLEManager.swift index 45a9bdaf..d6eaa58e 100644 --- a/MeshtasticClient/Views/Helpers/BLEManager.swift +++ b/MeshtasticClient/Views/Helpers/BLEManager.swift @@ -2,19 +2,32 @@ import Foundation import CoreBluetooth struct Peripheral: Identifiable { - let id: Int + let id: String + let index: Int let name: String let rssi: Int } -class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate { +//--------------------------------------------------------------------------------------- +// Meshtastic BLE Device Manager +//--------------------------------------------------------------------------------------- +class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { + + // Data + private var centralManager: CBCentralManager! + @Published var connectedPeripheral: CBPeripheral! + @Published var peripheralArray = [CBPeripheral]() + private var rssiArray = [NSNumber]() + private var timer = Timer() - var myCentral: CBCentralManager! - private var meshtasticPeripheral: CBPeripheral! @Published var isSwitchedOn = false @Published var peripherals = [Peripheral]() - let meshtasticServiceID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD") + var TORADIO_characteristic: CBCharacteristic! + var FROMRADIO_characteristic: CBCharacteristic! + var FROMNUM_characteristic: CBCharacteristic! + + let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD") let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7") let FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5") let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") //Notify @@ -22,12 +35,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate { override init() { super.init() - myCentral = CBCentralManager(delegate: self, queue: nil) - myCentral.delegate = self + centralManager = CBCentralManager(delegate: self, queue: nil) + centralManager.delegate = self } - + //--------------------------------------------------------------------------------------- + // Check for Bluetooth Connectivity + //--------------------------------------------------------------------------------------- func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { isSwitchedOn = true @@ -36,49 +51,169 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate { isSwitchedOn = false } } - - + + //--------------------------------------------------------------------------------------- + // Scan for nearby BLE devices using the Meshtastic BLE service ID + //--------------------------------------------------------------------------------------- + func startScanning() { + // Remove Existing Data + peripherals.removeAll() + peripheralArray.removeAll() + rssiArray.removeAll() + // Start Scanning + print("Start Scanning") + centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID]) + // scanningLabel.text = "Scanning..." } + //Timer.scheduledTimer(withTimeInterval: 15, repeats: false) {_ in + // self.stopScanning() + //} + } + + //--------------------------------------------------------------------------------------- + // Stop Scanning For BLE Devices + //--------------------------------------------------------------------------------------- + func stopScanning() { + print("Stop Scanning") + centralManager.stopScan() + } + + //--------------------------------------------------------------------------------------- + // Connect to a Device via UUID + //--------------------------------------------------------------------------------------- + func connectToDevice(id: String) { + connectedPeripheral = peripheralArray.filter({ $0.identifier.uuidString == id }).first + centralManager?.connect(connectedPeripheral!) + } + + //--------------------------------------------------------------------------------------- + // Disconnect Device function + //--------------------------------------------------------------------------------------- + func disconnectDevice(){ + if connectedPeripheral != nil { + centralManager?.cancelPeripheralConnection(connectedPeripheral!) + } + } + + //--------------------------------------------------------------------------------------- + // Discover Peripheral Event + //--------------------------------------------------------------------------------------- func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { - print(peripheral) - + //print(peripheral) + if peripheralArray.contains(peripheral) { + print("Duplicate Found.") + } else { + print("Adding peripheral: " + peripheral.name!); + peripheralArray.append(peripheral) + rssiArray.append(RSSI) + } + var peripheralName: String! - - if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String { - peripheralName = name + peripheralName = peripheral.name + if peripheral.name == nil { + if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String { + peripheralName = name + } + else { + peripheralName = "Unknown" + } } - else { - peripheralName = "Unknown" - } - - let newPeripheral = Peripheral(id: peripherals.count, name: peripheralName, rssi: RSSI.intValue) - print(newPeripheral) + + let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, index: peripherals.count, name: peripheralName, rssi: RSSI.intValue) + //print(newPeripheral) peripherals.append(newPeripheral) } - func startScanning() { - print("startScanning") - peripherals = []; - myCentral.scanForPeripherals(withServices: [meshtasticServiceID]) - } - - func stopScanning() { - print("stopScanning") - myCentral.stopScan() + //--------------------------------------------------------------------------------------- + // Connect Peripheral Event + //--------------------------------------------------------------------------------------- + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + print("Peripheral connected: " + peripheral.name!) + peripheral.delegate = self + peripheral.discoverServices(nil) + self.startScanning() } - func connectToDevice(uuid:String) { + //--------------------------------------------------------------------------------------- + // Disconnect Peripheral Event + //--------------------------------------------------------------------------------------- + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) + { + if(peripheral.identifier == connectedPeripheral.identifier){ + connectedPeripheral = nil + } + print("Peripheral disconnected: " + peripheral.name!) + self.startScanning() + } + + //--------------------------------------------------------------------------------------- + // Discover Services Event + //--------------------------------------------------------------------------------------- + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { - // let meshtasticPeripheral = self.peripherals.first(where: { $0.id == uuid }) - if (meshtasticPeripheral == nil) { - return + guard let services = peripheral.services else { return } + + for service in services + { + print("Service discovered: " + service.uuid.uuidString) + + if (service.uuid == meshtasticServiceCBUUID) + { + print ("Meshtastic service OK") + + peripheral.discoverCharacteristics(nil, for: service) } - // Attempt to connect to this device - myCentral.connect(meshtasticPeripheral, options: nil) - - // Retain the peripheral - + } } + //--------------------------------------------------------------------------------------- + // Discover Characteristics Event + //--------------------------------------------------------------------------------------- + func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + guard let characteristics = service.characteristics else { return } + + for characteristic in characteristics { + + switch characteristic.uuid + { + case TORADIO_UUID: + print("TORADIO characteristic OK") + TORADIO_characteristic = characteristic + // var toRadio: ToRadio = ToRadio() + // toRadio.wantConfigID = 32168 + // let binaryData: Data = try! toRadio.serializedData() + // peripheral.writeValue(binaryData, for: characteristic, type: .withResponse) + break + + case FROMRADIO_UUID: + print("FROMRADIO characteristic OK") + FROMRADIO_characteristic = characteristic + peripheral.readValue(for: FROMRADIO_characteristic) + break + + case FROMNUM_UUID: + print("FROMNUM (Notify) characteristic OK") + FROMNUM_characteristic = characteristic + peripheral.setNotifyValue(true, for: characteristic) + break + + default: + break + } + + } + } + //--------------------------------------------------------------------------------------- + // Update Characteristic Event + //--------------------------------------------------------------------------------------- + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, + error: Error?) { + switch characteristic.uuid { + case FROMRADIO_UUID: + print(characteristic.value ?? "no value") + default: + print("Unhandled Characteristic UUID: \(characteristic.uuid)") + } + } }