here is something that keeps me awake for three days already: I'm writing a little app that connects via BlueTooth to an Arduino. To get visual feedback about the connection state and the transmitted data, I use a view that allows me to connect/disconnect as well as shows me the state and data:
VStack {
Text("Glove Training App")
.font(.title)
HStack {
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange)
.font(.subheadline)
.padding(.bottom, 30)
Text(peripheral.transmittedString)
.font(.subheadline)
.padding(.bottom, 30)
}
}
In a separate file I have all the BT management:
class MyBluetoothManager: NSObject, ObservableObject {
@Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
(...)
func setConnected(peripheral: CBPeripheral) {
(...)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
class MyPeripheralDelegate: NSObject, ObservableObject, CBPeripheralDelegate {
let objectWillChange = ObservableObjectPublisher()
var transmittedString: String = "No data" {
willSet { objectWillChange.send()
}
}
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
(...)
let rxData = characteristic.value
if let str = NSString(data: rxData!, encoding: String.Encoding.utf8.rawValue) as String? {
print(str)
self.transmittedString = str
let measurement = str.components(separatedBy: "|")
(...)
} else {
print("not a valid UTF-8 sequence")
}
}
}
The values are initially set correctly, but then never updated. In the terminal I can see the printed values and the app works otherwise as expected. I'm on the latest version of XCode. I looked at several tutorials, and this seems to be tricky. Any help would be highly appreciated.
Cheers, Christian
EDIT: Here is the full BluetoothManager class (not my code mostly but works fine):
class MyBluetoothManager: NSObject, ObservableObject {
@Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
var state = State.poweredOff
enum State {
case poweredOff
case restoringConnectingPeripheral(CBPeripheral)
case restoringConnectedPeripheral(CBPeripheral)
case disconnected
case scanning(Countdown)
case connecting(CBPeripheral, Countdown)
case discoveringServices(CBPeripheral, Countdown)
case discoveringCharacteristics(CBPeripheral, Countdown)
case connected(CBPeripheral)
case outOfRange(CBPeripheral)
var peripheral: CBPeripheral? {
switch self {
case .poweredOff: return nil
case .restoringConnectingPeripheral(let p): return p
case .restoringConnectedPeripheral(let p): return p
case .disconnected: return nil
case .scanning: return nil
case .connecting(let p, _): return p
case .discoveringServices(let p, _): return p
case .discoveringCharacteristics(let p, _): return p
case .connected(let p): return p
case .outOfRange(let p): return p
}
}
}
func scan() {
guard central.state == .poweredOn else {
self.stateChange = "Cannot scan, BT is not powered on"
print("Cannot scan, BT is not powered on")
return
}
central.scanForPeripherals(withServices: [myDesiredServiceId], options: nil)
state = .scanning(Countdown(seconds: 10, closure: {
self.central.stopScan()
self.state = .disconnected
self.stateChange = "Scan timed out"
print("Scan timed out")
}))
}
func disconnect(forget: Bool = false) {
if let peripheral = state.peripheral {
central.cancelPeripheralConnection(peripheral)
}
if forget {
UserDefaults.standard.removeObject(forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
}
self.stateChange = "Disconnected"
state = .disconnected
}
func connect(peripheral: CBPeripheral) {
central.connect(peripheral, options: nil)
state = .connecting(peripheral, Countdown(seconds: 10, closure: {
self.central.cancelPeripheralConnection(peripheral)
self.state = .disconnected
self.stateChange = "Connect timed out"
print("Connect timed out")
}))
}
func discoverServices(peripheral: CBPeripheral) {
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverServices([myDesiredServiceId])
state = .discoveringServices(peripheral, Countdown(seconds: 10, closure: {
self.disconnect()
self.stateChange = "Could not discover services"
print("Could not discover services")
}))
}
func discoverCharacteristics(peripheral: CBPeripheral) {
guard let myDesiredService = peripheral.myDesiredService else {
self.disconnect()
return
}
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverCharacteristics([myDesiredCharacteristicId],
for: myDesiredService)
state = .discoveringCharacteristics(peripheral, Countdown(seconds: 10,
closure: {
self.disconnect()
self.stateChange = "Could not discover characteristics"
print("Could not discover characteristics")
}))
}
func setConnected(peripheral: CBPeripheral) {
guard let myDesiredCharacteristic = peripheral.myDesiredCharacteristic
else {
self.stateChange = "Missing characteristic"
print("Missing characteristic")
disconnect()
return
}
UserDefaults.standard.set(peripheral.identifier.uuidString,
forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.setNotifyValue(true, for: myDesiredCharacteristic)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange) << why don't you use MyBluetoothManager.shared here ? is there a second instance? this might be the error...but unfortunately you just showed us a small piece of code...