diff --git a/Info_extra_tags.plist b/Info_extra_tags.plist index a78e075..08b6cf1 100644 --- a/Info_extra_tags.plist +++ b/Info_extra_tags.plist @@ -3,8 +3,8 @@ NSBluetoothAlwaysUsageDescription - This app uses Bluetooth to connect to devices. + This app uses Bluetooth to connect to the collar tag. NSBluetoothPeripheralUsageDescription - This app uses Bluetooth. + This app uses Bluetooth to connect to the collar tag. diff --git a/Sources/scoobytag/BluetoothManager.swift b/Sources/scoobytag/BluetoothManager.swift index d8dcdbe..9573e4e 100644 --- a/Sources/scoobytag/BluetoothManager.swift +++ b/Sources/scoobytag/BluetoothManager.swift @@ -1,8 +1,14 @@ import SwiftUI import CoreBluetooth +struct Constants { + static let scbtSvcUuid = "00010F2C-DEAD-BEEF-A455-DEAD69BEEF69"; + static let scbtPetUuid: String = "00010F2D-DEAD-BEEF-A455-DEAD69BEEF69"; +} + final class BluetoothManager: NSObject, ObservableObject { - private var periphManager: CBPeripheralManager! + private var centralManager: CBCentralManager! + private var myPeripheral: CBPeripheral? @Published var status = "Starting..." override init() { @@ -11,22 +17,135 @@ final class BluetoothManager: NSObject, ObservableObject { } } -extension BluetoothManager: CBPeripheralManagerDelegate { - func peripheralManagerDidUpdateState(_ central: CBPeripheralManager) { - switch central.state { - case .poweredOn: - status = "Broadcasting" - let uuid = CBUUID(string: "12345678-1234-1234-1234-123456789ABC") - periphManager.startAdvertising( - [ - CBAdvertisementDataServiceUUIDsKey: [uuid], - CBAdvertisementDataLocalNameKey: "scbt-Test" - ]) - case .poweredOff: - status = "Bluetooth off" +extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { + func centralManagerDidUpdateState(_ central: CBCentralManager) { + guard central.state == .poweredOn else { + status = "Bluetooth OFF" + return + } - default: - status = "Bluetooth unavailable" + let uuid = CBUUID(string: Constants.scbtSvcUuid) + centralManager.scanForPeripherals( + withServices: [uuid], + options: [ + CBCentralManagerScanOptionAllowDuplicatesKey: false + ] + ) + } + + // Discovery callback + func centralManager( + _ central: CBCentralManager, + didDiscover peripheral: CBPeripheral, + advertisementData: [String: Any], + rssi RSSI: NSNumber + ) { + if (peripheral.name != nil) { + status = "Found: \(peripheral.name!)" + } else { + status = "Found: \(peripheral.identifier)" + } + + myPeripheral = peripheral + central.stopScan() + central.connect(peripheral) + } + + // Connection callback + func centralManager( + _ central: CBCentralManager, + didConnect peripheral: CBPeripheral + ) { + status = "Connected" + let uuid = CBUUID(string: Constants.scbtSvcUuid) + peripheral.discoverServices([uuid]) + peripheral.delegate = self + } + + // Connection failure callback + func centralManager( + _ central: CBCentralManager, + didFailToConnect peripheral: CBPeripheral, + error: Error? + ) { + let error2: String + if (error == nil) { error2 = "unknown" } + else { error2 = "\(error!)" } + status = "Failed: \(error2)" + } + + // Disconnection callback + func centralManager( + _ central: CBCentralManager, + didDisconnectPeripheral peripheral: CBPeripheral, + error: Error? + ) { + let error2: String + if (error == nil) { error2 = "unknown" } + else { error2 = "\(error!)" } + status = "Disconnected: \(error2)" + } + + // Service callback + func peripheral( + _ peripheral: CBPeripheral, + didDiscoverServices error: Error? + ) { + guard let services = peripheral.services else { return } + + status = "Service found" + + let scbtSvc = CBUUID(string: Constants.scbtSvcUuid) + let scbtPet = CBUUID(string: Constants.scbtPetUuid) + + for service in services { + // Ignore non scbtSvc + if (service.uuid != scbtSvc) { continue } + + peripheral.discoverCharacteristics([scbtPet], for: service) + } + } + + // Characteristic callback + func peripheral( + _ perihperal: CBPeripheral, + didDiscoverCharacteristicsFor service: CBService, + error: Error? + ) { + if (error != nil) { + status = "\(error!)" + return + } + + guard let characteristics = service.characteristics else { return } + + status = "Characteristics found (\(characteristics.count))" + + let scbtPet = CBUUID(string: Constants.scbtPetUuid) + + for characteristic in characteristics { + // Ignore non pet + if (characteristic.uuid != scbtPet) { continue } + + var value: UInt32 = 0 + let data = Data(bytes: &value, count: MemoryLayout.size) + + perihperal.writeValue(data, for: characteristic, type: .withResponse) + status = "Written \(data)" + } + } + + // Characteristic write callback + func peripheral( + _ perihperal: CBPeripheral, + didWriteValueFor characteristic: CBCharacteristic, + error: (any Error)? + ) { + if (error == nil) { + status = "Write success" + } + else { + status = "Write failed: \(error!)" } } }