From 1a777145e6ad3538562a835d503fc2d2c44c7bc1 Mon Sep 17 00:00:00 2001 From: Diogo Cruz Diniz Date: Sat, 25 Jul 2026 17:24:54 +0100 Subject: [PATCH] feat: Buttons for LED on and off --- .sourcekit-lsp/config.json | 2 +- Info_extra_tags.plist | 2 + Package.swift | 1 - Sources/scoobytag/BluetoothManager.swift | 85 ++++++++++++++++-------- Sources/scoobytag/ContentView.swift | 11 +-- 5 files changed, 67 insertions(+), 34 deletions(-) diff --git a/.sourcekit-lsp/config.json b/.sourcekit-lsp/config.json index e7c9738..1e535c7 100644 --- a/.sourcekit-lsp/config.json +++ b/.sourcekit-lsp/config.json @@ -1,5 +1,5 @@ { "swiftPM": { - "swiftSDK": "arm64-apple-ios" + "swiftSDK": "darwin" } } diff --git a/Info_extra_tags.plist b/Info_extra_tags.plist index 08b6cf1..788546d 100644 --- a/Info_extra_tags.plist +++ b/Info_extra_tags.plist @@ -6,5 +6,7 @@ This app uses Bluetooth to connect to the collar tag. NSBluetoothPeripheralUsageDescription This app uses Bluetooth to connect to the collar tag. + UIBackgroundModes + bluetooth-central diff --git a/Package.swift b/Package.swift index afd682b..6b0d4b1 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,6 @@ let package = Package( name: "ScoobyTag", platforms: [ .iOS(.v15), - .macOS(.v14), ], products: [ // An xtool project should contain exactly one library product, diff --git a/Sources/scoobytag/BluetoothManager.swift b/Sources/scoobytag/BluetoothManager.swift index 9573e4e..3a8d71a 100644 --- a/Sources/scoobytag/BluetoothManager.swift +++ b/Sources/scoobytag/BluetoothManager.swift @@ -8,12 +8,39 @@ struct Constants { final class BluetoothManager: NSObject, ObservableObject { private var centralManager: CBCentralManager! - private var myPeripheral: CBPeripheral? + private var tagPerihp: CBPeripheral? + private var scbtSvc: CBService? + private var petChar: CBCharacteristic? @Published var status = "Starting..." override init() { super.init() - periphManager = CBPeripheralManager(delegate: self, queue: nil) + centralManager = CBCentralManager( + delegate: self, + queue: nil, + //options: [ + // CBCentralManagerOptionRestoreIdentifierKey: "DogCollarCentral" + //] + ) + } + + func led_on() { + pet_write(0) + } + + func led_off() { + pet_write(1) + } + + func pet_write(_ value: UInt32) { + if tagPerihp == nil || petChar == nil { + status = "Not connected!" + return + } + + var lvalue = value + let data = Data(bytes: &lvalue, count: MemoryLayout.size) + tagPerihp!.writeValue(data, for: petChar!, type: .withResponse) } } @@ -40,13 +67,9 @@ extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { advertisementData: [String: Any], rssi RSSI: NSNumber ) { - if (peripheral.name != nil) { - status = "Found: \(peripheral.name!)" - } else { - status = "Found: \(peripheral.identifier)" - } - - myPeripheral = peripheral + let name = peripheral.name ?? peripheral.identifier.uuidString + status = "Found \(name)" + tagPerihp = peripheral central.stopScan() central.connect(peripheral) } @@ -56,7 +79,8 @@ extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { _ central: CBCentralManager, didConnect peripheral: CBPeripheral ) { - status = "Connected" + let name = peripheral.name ?? peripheral.identifier.uuidString + status = "Connected to \(name)" let uuid = CBUUID(string: Constants.scbtSvcUuid) peripheral.discoverServices([uuid]) peripheral.delegate = self @@ -72,6 +96,9 @@ extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { if (error == nil) { error2 = "unknown" } else { error2 = "\(error!)" } status = "Failed: \(error2)" + + tagPerihp = nil + petChar = nil } // Disconnection callback @@ -84,6 +111,9 @@ extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { if (error == nil) { error2 = "unknown" } else { error2 = "\(error!)" } status = "Disconnected: \(error2)" + + tagPerihp = nil + petChar = nil } // Service callback @@ -93,16 +123,18 @@ extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { ) { guard let services = peripheral.services else { return } - status = "Service found" - - let scbtSvc = CBUUID(string: Constants.scbtSvcUuid) - let scbtPet = CBUUID(string: Constants.scbtPetUuid) + let svcUUID = CBUUID(string: Constants.scbtSvcUuid) + let petUUID = CBUUID(string: Constants.scbtPetUuid) for service in services { - // Ignore non scbtSvc - if (service.uuid != scbtSvc) { continue } + if (service.uuid == svcUUID) { + scbtSvc = service + peripheral.discoverCharacteristics([petUUID], for: service) + } + } - peripheral.discoverCharacteristics([scbtPet], for: service) + if scbtSvc == nil { + status = "Could not find scbt service" } } @@ -118,20 +150,17 @@ extension BluetoothManager: CBCentralManagerDelegate, CBPeripheralDelegate { } 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 } + for characteristic: CBCharacteristic in characteristics { + if (characteristic.uuid == scbtPet) { + petChar = characteristic + status = "Found pet characteristic" + } + } - var value: UInt32 = 0 - let data = Data(bytes: &value, count: MemoryLayout.size) - - perihperal.writeValue(data, for: characteristic, type: .withResponse) - status = "Written \(data)" + if petChar == nil { + status = "Could not find pet characteristic" } } diff --git a/Sources/scoobytag/ContentView.swift b/Sources/scoobytag/ContentView.swift index dffd709..72fb0b0 100644 --- a/Sources/scoobytag/ContentView.swift +++ b/Sources/scoobytag/ContentView.swift @@ -5,12 +5,15 @@ struct ContentView: View { var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") Text(bluetoothManager.status) + Button(action: bluetoothManager.led_on) { + Text("LED On") + } + Button(action: bluetoothManager.led_off) { + Text("LED Off") + } } .padding() + .buttonStyle(BorderedButtonStyle()) } }