Hi there, I'm having an issue hoping someone could help. We have an iOS app that uses CoreBluetooth to connect to peripherals using the central manager. The app works great - However, when using the same exact central manager for our watchos app, it will attempt to connect, but I never get a callback for either didConnect or didFailToConnect.
The watch can connect successfully to other BLE devices, so the watch itself is capable of BLE connectivity.
Here's a list of thing's I've tried (unsuccessfully):
1) Added every bluetooth-related entitlement to info.plist
- Privacy - Bluetooth Always Usage Description
- Privacy - Bluetooth Peripheral Usage Description
- Background Modes: App communicates using CoreBluetooth, App shares data using CoreBluetooth
2) Checked for Single-Connection Limits
- Verified that the iPhone was fully disconnected from the peripheral to ensure the device wasn’t limited to one connection.
- Attempted to connect on watchOS alone (with iPhone turned off)
3) Tried various options for CBCentralManager, scanForPeripherals, and connect
- I went through all the keys for various options and tried just setting them, they had no effect
- CBCentralManagerOptionShowPowerAlertKey, CBConnectPeripheralOptionEnableTransportBridgingKey
- Item 2
4) Tried .registerForConnectionEvents()
5) Set peripheral's delegate to the central in the didDiscover, stored it in a variable to ensure a strong reference to it
I get no warnings either. The last time I ran into something like this, I found out the watchOS blocks TCP sockets. If I print out the CBPeripheralState a few seconds after trying to connect, it shows its stuck on CBPeripheralStateConnecting.
Any advice or direction is greatly appreciated
Below is the code and various print outs (day 2 into debugging, so it's not pretty)
class WatchBLEManager:NSObject,CBCentralManagerDelegate, ObservableObject{
var centralManager: CBCentralManager?
@Published var devices : [String:AtomBLEDevice] = [:]
private var scanningDevice:AtomBLEDevice?
var bleStatus:WatchBLEStatus = .blePoweredOff
func startBLE() {
centralManager = CBCentralManager(delegate: self, queue: nil,options: [CBCentralManagerOptionShowPowerAlertKey: true])
self.centralManager?.delegate = self
}
func startScan() {
self.centralManager?.scanForPeripherals(withServices: [],options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
self.centralManager?.delegate = self
}
func stopScan() {
print("stopping scan")
self.centralManager?.stopScan()
filterName = ""
scanningDevice = nil
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch (central.state) {
//... other states omitted
case .poweredOff:
bleStatus = .blePoweredOff
// bleStateDelegate?.didBlePoweredOff()
for device in devices.values{
device.isConnected = false
}
print("BLE is Powered Off")
case .poweredOn:
bleStatus = .blePoweredOn
// bleStateDelegate?.didBlePoweredOn()
startScan()
centralManager?.registerForConnectionEvents()
print("Central supports extended scan and connect: ", CBCentralManager.supports(.extendedScanAndConnect))
print("powered on")
@unknown default:
print("BLE is Unknown")
}
}
private let connectionQueue = DispatchQueue(label: "com.atom.connectionQueue")
var connectingTo: String? = nil
var peripheral: CBPeripheral? = nil
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String else { return}
if localName.contains("Atom") {
print("\nConnecting to \(localName)")
print("\tAdvertising data: \(advertisementData)")
print("\tANCS Authorized: ",peripheral.ancsAuthorized)
print("\tServices", peripheral.services, "\n")
self.peripheral = peripheral
self.peripheral?.delegate = self
// central.registerForConnectionEvents()
// central.delegate = self
peripheral.delegate = self
DispatchQueue.main.async {
// central.connect(peripheral)
self.centralManager?.connect(peripheral, options: [ CBConnectPeripheralOptionEnableTransportBridgingKey: true])
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
print("\tState", String(describing: peripheral.state))
print("Connected Peripherals: \(self.centralManager?.retrieveConnectedPeripherals(withServices: []))")
}
}
}
// Never gets called for watchos
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to peripheral: \(peripheral.identifier)")
if let atomDevice = getAtomBLEDevice(peripheral: peripheral) {
//atomDevice.setPeripheral(perpipheral: <#T##CBPeripheral?#>)
atomDevice.isConnected = true
atomDevice.isConnecting = false
//delegate?.didConnected(atomBLE: atomDevice!)
atomDevice.startDiscoveringService()
//atomDevice?.delegate?.didConnected(atomBLE: atomDevice!)
print("Connected: \(peripheral.name)")
} else {
print("no matching atom device found for didConnect")
print("connected peripheral :",peripheral.identifier.uuidString)
}
}
func centralManager(_ central: CBCentralManager, connectionEventDidOccur event: CBConnectionEvent, for peripheral: CBPeripheral) {
print("Connection event: \(event)")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) {
print("Failed to connect: \(error?.localizedDescription)")
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
let atomDevice = getAtomBLEDevice(peripheral: peripheral)
atomDevice?.isConnected = false
print("Peripheral disconnected:\(peripheral.name)")
}
func clearData() {
filterName = ""
for device in devices.values{
disconnect(atomBLEDevice: device)
device.perpipheral?.delegate = nil
}
devices = [:]
scanningDevice = nil
// delegate = nil
centralManager = nil
}
}
extension WatchBLEManager: CBPeripheralDelegate {
}```