IOS vs OSX BLE: Instantiating CBCentralManager - coding for iOS - ios

I'm learning iOS vs OSX BLE.
I notice that I can't instantiate CBCentralManager in iOS because of:
[CoreBluetooth] XPC connection invalid
Unsupported
Versus via OSX because the iOS platform doesn't have the 'App Sandbox' Characteristic where I can set for BLE use.
Here's my iOS Code:
import SwiftUI
struct ContentView: View {
#ObservedObject var bleManager = BLEManager()
var body: some View {
ZStack {
Color("Background")
Text("Hello")
}
}
}
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
var centralManager: CBCentralManager!
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("power is on")
case .resetting:
print("Resetting")
case .unsupported:
print("Unsupported")
case .unauthorized:
print("UnAuthorized")
case .unknown:
print("UnKnown")
case .poweredOff:
print("Powered OFF")
#unknown default:
print("**** Default ****")
}
}
}
Here's the required .plist entry:
I understand that iPhones can be either a BLE center or peripheral.
Simple Question: How do I code for a bona fide CBCentralMaster for iOS?
I'm merely taking baby steps here: coding for peripheral detection.
... then continue from there.

Are you running on the simulator (where it is unsupported) instead of on device?

See the main Core Bluetooth docs:
Your app will crash if its Info.plist doesn’t include usage description keys for the types of data it needs to access. To access Core Bluetooth APIs on apps linked on or after iOS 13, include the NSBluetoothAlwaysUsageDescription key. In iOS 12 and earlier, include NSBluetoothPeripheralUsageDescription to access Bluetooth peripheral data.

Related

Multiple CBCentralManagers in the same app not calling their update state functions

There are a few Stack Overflow questions about this however they are either outdated or have mixed answers on whether 2 central managers are supported. I am using IOS 16.2, the problem also occurs on IOS 16.1. Xcode version 14.1.
PROBLEM:
When attempting to initialize multiple CBCentralManagers, only the first CBCentralManager calls the centralManagerDidUpdateState() function to update its state, the other managers have their state always at unknown.
DESIRED STATE:
All central managers call the centralManagerDidUpdateState() function of their respective delegates. In the code provided, the second central manager should print "SECOND MANAGER UPDATED STATE."
BACKGROUND:
The reason I am using multiple central managers is because I am using a package that initializes its own Core Bluetooth central manager (https://github.com/NordicSemiconductor/IOS-nRF-Mesh-Library/blob/main/nRFMeshProvision/Classes/Bearer/GATT/BaseGattProxyBearer.swift#L109). I noticed the code wasn't doing what it was supposed to (and documented to do). The documentation states that the object in the link I provided above can be initialized within the didDiscover function of a user defined central manager's delegate (https://github.com/NordicSemiconductor/IOS-nRF-Mesh-Library/blob/main/Documentation.docc/nRFMeshProvision.md#provisioning). I investigated enough to find the problem lies in having multiple central managers. A workaround I can pursue is using only 1 central manager and passing it to the package, but that would require me to edit the source and perhaps lead to more cumbersome unsupported workarounds with it. The package was documented to be working in September of 2022, so I don't know if Apple changed the way Core Bluetooth CentralManagers behave to only allow 1 working at a time, or if there may be something wrong with my environment.
Here is my code to replicate the issue easily.
CODE:
ModelData, where the central managers are initialized:
import Foundation
import Combine
import CoreBluetooth
final class ModelData: {
let centralManager: CBCentralManager!
let secondCentralManager: CBCentralManager!
init() {
let centralManagerDelegate = CentralManagerDelegate()
centralManager = CBCentralManager(delegate: centralManagerDelegate, queue: nil)
let secondCMDelegate = SecondCentralManagerDelegate()
secondCentralManager = CBCentralManager(delegate: secondCMDelegate, queue: nil)
print("initialized central managers")
}
}
Central Manager Delegate:
import Foundation
import CoreBluetooth
class CentralManagerDelegate: NSObject, ObservableObject, CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("Bluetooth Device is UNKNOWN")
case .unsupported:
print("Bluetooth Device is UNSUPPORTED")
case .unauthorized:
print("Bluetooth Device is UNAUTHORIZED")
case .resetting:
print("Bluetooth Device is RESETTING")
case .poweredOff:
print("Bluetooth Device is POWERED OFF")
case .poweredOn:
print("Bluetooth Device is POWERED ON")
#unknown default:
print("Unknown State")
}
}
}
Second Central Manager Delegate:
import Foundation
import CoreBluetooth
class SecondCentralManagerDelegate: NSObject, ObservableObject, CBCentralManagerDelegate {
func centralManagerDidUpdateState(\_ central: CBCentralManager) {
print("SECOND MANAGER UPDATED STATE")
}
}
The view calling the modelData:
struct HomeScreen: View {
#StateObject private var modelData = ModelData()
#State private var showDetail = true
var body: some View {
}
}
Output when running:
initialized central managers
Bluetooth Device is POWERED ON
You are creating your delegate objects as local variables and since the CBCentralManager doesn't retain its delegate, these will be released as soon as init returns. You are probably just lucky that the first delegate has an opportunity to print the state before it is released. The second delegate is released too soon.
You can easily confirm this by adding a deinit method to your delegate classes that prints something - You will see your delegates being de-initialised.
The solution is to use a property to retain a strong reference to your delegates as you do with the CBCentralManager instances
final class ModelData: {
let centralManager: CBCentralManager!
let secondCentralManager: CBCentralManager!
let centralManagerDelegate: CentralManagerDelegate
let secondCMDelegate: SecondCentralManagerDelegate
init() {
centralManagerDelegate = CentralManagerDelegate()
centralManager = CBCentralManager(delegate: centralManagerDelegate, queue: nil)
secondCMDelegate = SecondCentralManagerDelegate()
secondCentralManager = CBCentralManager(delegate: secondCMDelegate, queue: nil)
}
}

CoreBluetooth peripheral not appearing in a WebBluetooth scan

I am looking to build an iOS application that uses CoreBluetooth to act as a BLE peripheral and a web app that uses WebBluetooth to scan for, find and exchange messages with the iPhone app.
I started off with this Swift code:
import SwiftUI
import CoreBluetooth
class BLE: NSObject, CBPeripheralManagerDelegate {
var peripheralManager: CBPeripheralManager!
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .unknown: print("unknown")
case .resetting: print("resetting")
case .unsupported: print("unsupported")
case .unauthorized: print("unauthorized")
case .poweredOff:
print("poweredOff")
peripheral.stopAdvertising()
case .poweredOn:
print("poweredOn")
let characteristic1 = CBMutableCharacteristic(
type: CBUUID(nsuuid: UUID()),
properties: [.notify, .write, .read],
value: nil,
permissions: [.readable, .writeable]
)
let characteristic2 = CBMutableCharacteristic(
type: CBUUID(nsuuid: UUID()),
properties: [.read],
value: "test".data(using: .utf8),
permissions: [.readable]
)
let serviceUuid = CBUUID(nsuuid: UUID())
let service = CBMutableService(type: serviceUuid, primary: true)
service.characteristics = [characteristic1, characteristic2]
peripheralManager.add(service)
//let uuid = CBUUID(string: "14b50a17-51be-478f-9b24-7efa8d2a083f")
peripheral.startAdvertising([
CBAdvertisementDataLocalNameKey: "blething",
CBAdvertisementDataServiceUUIDsKey: [serviceUuid]
])
default: print("???")
}
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
print(error)
}
}
#main
struct BTtestApp: App {
let ble = BLE()
var body: some Scene {
WindowGroup {
Text("Hello, world!")
.padding()
}
}
}
I believe this is all of the code needed to make my iOS app BLE peripheral discoverable. I run this app on my phone and the advertisement call seems to go through without any runtime errors or crashes. The error printed is nil.
However, when I use https://googlechrome.github.io/samples/web-bluetooth/scan.html?allAdvertisements=true and scan for advertisements, nothing at all comes up.
When I uncheck All Advertisements and fill in Device Name to say blething, nothing comes up still.
I tried using both uuid and [uuid] (an array) for the service UUIDs value as I am not sure which is correct. The docs say the type is string https://developer.apple.com/documentation/corebluetooth/cbadvertisementdataserviceuuidskey but also that it should be an array? Anyway, neither worked.
What is the problem? Why is my BLE peripheral implemented in software using CoreBluetooth not showing up?

How to scan non LE devices using Swift?

I have RFID reader, which is not LE device.
https://www.tsl.com/products/1153-bluetooth-wearable-uhf-rfid-reader
I'm trying to write an iOS application, scan this device and connect it using swift CoreBluetooth library but my App finds everything besides this device. How is it possible to scan this reader?
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate {
var manager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
manager = CBCentralManager(delegate: self, queue: nil)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
break;
case .poweredOff:
break;
case .poweredOn:
manager.scanForPeripherals(withServices: nil)
break;
case .resetting:
break;
case .unauthorized:
break;
case .unsupported:
break;
default:
break;
}
}
}
That device states that is MFi certified and uses the SPP profile, not the BLE GATT profile. This means that you will need to use the External Accessory Framework, not Core Bluetooth, to communicate with it.
You will need to the manufacturer provided iOS SDK for the device. If they do and you want to release your app on the App Store then they will also need to approve your app and supply some paperwork to Apple.
The device says that it also supports the HID profile, so perhaps you could just treat it as a keyboard; This doesn't require any code but isn't the best user experience.
You need to use CoreNFC to read RFID tags. No need to use CoreBluetooth at all.
https://developer.apple.com/documentation/corenfc

CBCentralManager iOS10 and iOS9

So I'm migrating to iOS10 but I also need my code to run on iOS9. I'm using CoreBluetooth and CBCentralManagerDelegate. I can get my code to work for iOS10 however I need the fallback to work for iOS9 as well.
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if #available(iOS 10.0, *) {
switch central.state{
case CBManagerState.unauthorized:
print("This app is not authorised to use Bluetooth low energy")
case CBManagerState.poweredOff:
print("Bluetooth is currently powered off.")
case CBManagerState.poweredOn:
print("Bluetooth is currently powered on and available to use.")
default:break
}
} else {
// Fallback on earlier versions
switch central.state{
case CBCentralManagerState.unauthorized:
print("This app is not authorised to use Bluetooth low energy")
case CBCentralManagerState.poweredOff:
print("Bluetooth is currently powered off.")
case CBCentralManagerState.poweredOn:
print("Bluetooth is currently powered on and available to use.")
default:break
}
}
}
I get the error:
Enum case 'unauthorized' is not a member of type 'CBManagerState'
On the line:
case CBCentralManagerState.unauthorized:
As well as for .poweredOff and .poweredOn.
Any ideas how I can get it to work in both cases?
You can simply omit the enumeration type name and just use the .value. This will compile without warnings and works on iOS 10 and earlier since the underlying raw values are compatible.
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state{
case .unauthorized:
print("This app is not authorised to use Bluetooth low energy")
case .poweredOff:
print("Bluetooth is currently powered off.")
case .poweredOn:
print("Bluetooth is currently powered on and available to use.")
default:break
}
}
I worked around this issue on Xcode 8 with Swift 2.3 (targeting iOS 8 and above) by creating an extension property on CBCentralManager which is of the old enum type, CBCentralManagerState. I named it centralManagerState. I refer to CBCentralManager.centralManagerState where I used to refer to CBCentralManager.state.
extension CBCentralManager {
internal var centralManagerState: CBCentralManagerState {
get {
return CBCentralManagerState(rawValue: state.rawValue) ?? .Unknown
}
}
}
I got the idea from this forum thread though they hadn't posted the code yet.
I contacted Apple about this and was given the following response (paraphrasing).
Due to the changing nature of swift, the above implementation is not possible however you can use the rawValue of the enum as the state is identical between the two classes. Therefore the following will work for now:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if #available(iOS 10.0, *) {
switch central.state{
case CBManagerState.unauthorized:
print("This app is not authorised to use Bluetooth low energy")
case CBManagerState.poweredOff:
print("Bluetooth is currently powered off.")
case CBManagerState.poweredOn:
print("Bluetooth is currently powered on and available to use.")
default:break
}
} else {
// Fallback on earlier versions
switch central.state.rawValue {
case 3: // CBCentralManagerState.unauthorized :
print("This app is not authorised to use Bluetooth low energy")
case 4: // CBCentralManagerState.poweredOff:
print("Bluetooth is currently powered off.")
case 5: //CBCentralManagerState.poweredOn:
print("Bluetooth is currently powered on and available to use.")
default:break
}
}
}
func centralManagerDidUpdateState(central: CBCentralManager)
{
if #available(iOS 10.0, *)
{
switch (central.state) {
case CBManagerState.PoweredOff:
print("CBCentralManagerState.PoweredOff")
case CBManagerState.Unauthorized:
// Indicate to user that the iOS device does not support BLE.
print("CBCentralManagerState.Unauthorized")
break
case CBManagerState.Unknown:
// Wait for another event
print("CBCentralManagerState.Unknown")
break
case CBManagerState.PoweredOn:
print("CBCentralManagerState.PoweredOn")
self.centralManager!.scanForPeripheralsWithServices([CBUUID(string:TRANSFER_UUID)], options:[CBCentralManagerScanOptionAllowDuplicatesKey: false])
case CBManagerState.Resetting:
print("CBCentralManagerState.Resetting")
case CBManagerState.Unsupported:
print("CBCentralManagerState.Unsupported")
break
}
}
else
{
switch central.state.rawValue
{
case 0: // CBCentralManagerState.Unknown
print("CBCentralManagerState.Unknown")
break
case 1: // CBCentralManagerState.Resetting
print("CBCentralManagerState.Resetting")
case 2:// CBCentralManagerState.Unsupported
print("CBCentralManagerState.Unsupported")
break
case 3: // CBCentralManagerState.unauthorized
print("This app is not authorised to use Bluetooth low energy")
break
case 4: // CBCentralManagerState.poweredOff:
print("Bluetooth is currently powered off.")
case 5: //CBCentralManagerState.poweredOn:
self.centralManager!.scanForPeripheralsWithServices([CBUUID(string:TRANSFER_UUID)], options:[CBCentralManagerScanOptionAllowDuplicatesKey: false])
print("Bluetooth is currently powered on and available to use.")
default:break
}
}
}

Swift: Why can't my iOS scanned other bluetooth devices

Now I'm currently doing an application project that needs my iPhone to scan other nearby bluetooth devices and list them out. I'm wondering is my code has any problem?
Code:
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate {
var manager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
manager = CBCentralManager (delegate: self, queue: nil)
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print("Peripheral: \(peripheral)")
}
func centralManagerDidUpdateState(central: CBCentralManager) {
print("Checking")
switch(central.state)
{
case.Unsupported:
print("BLE is not supported")
case.Unauthorized:
print("BLE is unauthorized")
case.Unknown:
print("BLE is Unknown")
case.Resetting:
print("BLE is Resetting")
case.PoweredOff:
print("BLE service is powered off")
case.PoweredOn:
print("BLE service is powered on")
print("Start Scanning")
manager.scanForPeripheralsWithServices(nil, options: nil)
default:
print("default state")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I'm using iPhone 5 (iOS 9) and I'm sure that my Bluetooth is turned on.
When I run the application in my iPhone, the console only log the following output:
Checking
BLE service is powered on
Start Scanning
But there is no Bluetooth device's name shown in the output. Even I turn on my iPad (iPad Mini 4 iOS 8) and the list still wouldn't update.
Sometimes it does scan my MacBook Pro Bluetooth and the output will have this:
Peripheral: <CBPeripheral: 0x14d70e00, identifier = 54738076-6C97-FD04-18CF-5E1AF6705865, name = vivien’s MacBook Pro, state = disconnected>
So, why is this happening? Can someone please explain to me?
case 1:
You must use GKSession to scan and connect with another iOS device,not CoreBluetooth.
case 2:
Your bluetooth device is a Bluetooth 3.0 accessory.Your iPhone can discover and show it in Setting->Bluetooth.
But this message isn't delivered to your app,so your app won't discover it.
Try again with a Bluetooth 4.0 accessory.

Resources