iOS: Get API MISUSE: Cancelling connection for unused peripheral - ios

I have a singleton class holding a CBPeripheral array. When a new device is discovered, it is added to that array. This array is used as a UITableView data source. When a table view cell is selected by the user, a connected is made to the selected CBPeripheral via fetch.
Unfortunately I get this error:
[CoreBluetooth] API MISUSE: Cancelling connection for unused peripheral <CBPeripheral: 0x1c0103e70, identifier = xxx, name = xxx, state = connecting>, Did you forget to keep a reference to it?
I searched stack overflow, there have been similar questions asked before, but all of them say the root cause is "not add peripheral to a strong reference, so iOS free it". But I have added them to an array.
Code:
class BLEManager:NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
static let sharedInstance = BLEManager()
var centralManager: CBCentralManager!
var connectedService: BLEDSPService?
var delegate: BLEDiscoveryDelegate?
var readUUIDString: String
var writeUUIDString: String
var serviceUUIDString: String
var connectedPeripheral: CBPeripheral?
var peripherals = [CBPeripheral]() {
didSet {
delegate?.peripheralDidDiscoverNewOnes()
}
}
init(serviceUUIDString: String, readUUIDString: String, writeUUIDString: String) {
self.readUUIDString = readUUIDString
self.writeUUIDString = writeUUIDString
self.serviceUUIDString = serviceUUIDString
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main);
}
internal func startScanning() {
let serviceUUID = CBUUID(string: serviceUUIDString)
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: 0]
centralManager.scanForPeripherals(withServices: [serviceUUID], options: options)
}
func stopScanning() {
centralManager.stopScan()
delegate?.peripheralDidStopScan()
}
open func connectTo(index: Int) {
if let current = connectedPeripheral {
disconnectPeripheral(current)
}
if index < peripherals.count {
connectPeripheral(peripherals[index])
stopScan()
}
}
#objc func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard let name = peripheral.name?.lowercased() else {
return
}
guard (name.contains(NamesManager.shared.scanName)) else {
return
}
if !peripherals.contains(peripheral) {
peripherals.append(peripheral)
delegate?.peripheralDidDiscovered()
}
}
But if I save peripheral to a var, then connect it. it works:
var connectedPeripheral: CBPeripheral?
open func connectTo(index: Int) {
if let current = connectedPeripheral {
connectPeripheral(current)
}
stopScan()
}
#objc func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard let name = peripheral.name?.lowercased() else {
return
}
guard (name.contains(NamesManager.shared.scanName)) else {
return
}
if !peripherals.contains(peripheral) {
connectedPeripheral = peripheral
peripherals.append(peripheral)
stopScanning()
delegate?.peripheralDidDiscovered()
}
}

Related

CoreBluetooth Functions not working from Singleton

So I currently got a bluetooth connection setup between a iPad and iPhone. I've created my testcode in the ViewController and everything works fine. Now I moved it to 2 manager classes one for the CBCentralManager and one for the CBPeripheralManager above those to classes I made a BluetoothManager which is a singleton class and holds some information regarding currently connected devices.
However when doing this I'm facing a problem it seems like the centralManager.connect() call doesn't actually work. I debugged my entire code and after that line nothing seems to happen and I can't seem to figure out why this is or where I'm actually going wrong.
The CentralManager class
import Foundation
import CoreBluetooth
class CentralManager: NSObject {
private var centralManager: CBCentralManager!
var peripherals: [CBPeripheral] = []
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
}
// MARK: - CBCentralManager Delegate Methods
extension CentralManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
centralManager.scanForPeripherals(withServices: [BLEConstants.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
default:
break
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !peripherals.contains(peripheral) {
peripheral.delegate = self
peripherals.append(peripheral)
centralManager.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices([BLEConstants.serviceUUID])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
guard let peripheralIndex = peripherals.index(of: peripheral), BluetoothManager.shared.deviceCharacteristic[peripheral] != nil else { return }
peripherals.remove(at: peripheralIndex)
BluetoothManager.shared.deviceCharacteristic.removeValue(forKey: peripheral)
}
}
// MARK: - CBPeripheral Delegate Methods
extension CentralManager: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
if service.uuid == BLEConstants.serviceUUID {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
let characteristic = characteristic as CBCharacteristic
if BluetoothManager.shared.deviceCharacteristic[peripheral] == nil {
BluetoothManager.shared.deviceCharacteristic[peripheral] = characteristic
}
}
}
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
}
}
The PeripheralManager class
class PeripheralManager: NSObject {
private var peripheralManager: CBPeripheralManager!
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
}
// MARK: - Manage Methods
extension PeripheralManager {
func updateAdvertising() {
guard !peripheralManager.isAdvertising else { peripheralManager.stopAdvertising(); return }
let advertisingData: [String: Any] = [CBAdvertisementDataServiceUUIDsKey: BLEConstants.serviceUUID,
CBAdvertisementDataLocalNameKey: BLEConstants.bleAdvertisementKey]
peripheralManager.startAdvertising(advertisingData)
}
func initializeService() {
let service = CBMutableService(type: BLEConstants.serviceUUID, primary: true)
let characteristic = CBMutableCharacteristic(type: BLEConstants.charUUID, properties: BLEConstants.charProperties, value: nil, permissions: BLEConstants.charPermissions)
service.characteristics = [characteristic]
peripheralManager.add(service)
}
}
// MARK: - CBPeripheralManager Delegate Methods
extension PeripheralManager: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
initializeService()
updateAdvertising()
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value {
let messageText = String(data: value, encoding: String.Encoding.utf8)
print(messageText ?? "")
}
self.peripheralManager.respond(to: request, withResult: .success)
}
}
}
The BluetoothManager class
class BluetoothManager {
static let shared = BluetoothManager()
private var centralManager: CentralManager!
private var peripheralManager: PeripheralManager!
var deviceCharacteristic: [CBPeripheral: CBCharacteristic] = [:]
var connectedPeripherals: [CBPeripheral] { return centralManager.peripherals }
func setup() {
centralManager = CentralManager()
peripheralManager = PeripheralManager()
}
}
and then in my ViewController didLoad I'm calling BluetoothManager.shared.setup()
Does anyone know why the devices don't seem to connect with eachother or maybe the delegate functions after that just don't get called?
The process starts when the static shared variable is initialized with BluetoothManager(). I am not sure when this happens in Swift, it is either at the very start of the program, or when you use BluetoothManager.setup for the first time.
Initialization of the variable calls the init() method of the BluetoothManager. This will instantiate a CentralManager, and its init() method will be called. This will instantiate a CBCentralManager, which will start the Bluetooth process.
Then you call setup(), which will instantiate a new CentralManager, with its own CBCentralManager. I can imagine something goes wrong with two CBCentralManager.
To solve it, don't use setup(), but initialize the variables in init() instead.
To debug this kind of situation, put breakpoints in all init() methods. Create destructors, and put breakpoints in those as well. Technically, you need destructors anyways, because you need to remove yourself as delegate from the CBCentralManager object.
Note also that you only call scanForPeripherals from centralManagerDidUpdateState. The CBCentralManager can already be in poweredOn state when it starts, this can happen when another app is using Bluetooth at the same time - or when your first CBCentralManager object has already started it. In that case, centralManagerDidUpdateState will never be called.
Are you sure your Singleton is correctly initialized?
Try this:
import Foundation
private let singleton = Singleton()
class Singleton {
static let sharedInstance : Singleton = {
return singleton
}()
let cnetralManager = = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}

iOS 11 Core Bluetooth restoration not working

I have an iOS app that uses Core Bluetooth to connect to a V.BTTN smart button. Everything worked perfectly fine in iOS 10, but since iOS 11 has come out the restoration process seems to break.
When I launch my app, with a previously paired button, the OS does in fact call the centralManager:willRestoreState, and the accompanying dictionary includes a pointer to a CBPeripheral object that has a status of connected. Just as it did back in iOS 10. However, the problem I am running into is when I call the discoverServices on the peripheral I am never returned any services in the peripheral:didDiscoverServices method. In fact that method, nor any other method, is called at all.
I have been searching all of the internet and have found people having similar issues with button pairing an connections, but those have generally been issues with improper lifecycle management of the peripheral object. I believe I have everything setup correct and at a loss. Does anyone have any idea what could be going on here?
import Foundation
import CoreBluetooth
class SmartButtonManager: NSObject {
//MARK: - Singleton
static let sharedManager = SmartButtonManager()
//MARK: - Properties
var discoveredDevices: [CBPeripheral] {
get {
return perfs
}
}
fileprivate(set) var isBluetoothOn = false
//MARK: - Private Constants & Variables
fileprivate var connectedButtonUUID: String?
fileprivate let queue = DispatchQueue(label: "V.BTTN", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
fileprivate var manager: CBCentralManager?
fileprivate var perfs = [CBPeripheral]()
fileprivate var connectedPerf: CBPeripheral?
fileprivate var isButtonReady = false
//MARK: - Initialization
override init() {
super.init()
}
//MARK: - Configure
func configure(withLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) {
// create new central manager
manager = CBCentralManager(delegate: self, queue: queue, options: [CBCentralManagerOptionRestoreIdentifierKey: managerIdentifier])
}
}
//MARK: - V.BTTN
extension SmartButtonManager: CBCentralManagerDelegate, CBPeripheralDelegate {
func scanForVbttn() {
perfs.removeAll()
manager?.scanForPeripherals(withServices: services, options: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOff:
print("[centralManagerDidUpdateState] CB BLE hardware is powered off")
perfs.removeAll()
isBluetoothOn = false
case .poweredOn:
print("[centralManagerDidUpdateState] CB BLE hardware is powered on. Start scanning for peripherals")
isBluetoothOn = true
case .unauthorized:
print("[centralManagerDidUpdateState] CB BLE hardware is not authorized")
case .unsupported:
print("[centralManagerDidUpdateState] CB BLE hardware is not supported")
isBluetoothOn = false
default:
print("[centralManagerDidUpdateState] CB BLE hardware state is unknown")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("[centralManager:didDiscover peripheral:advertisementData] CB BLE did discover peripheral with advertisement data: \(advertisementData)")
guard let perfName = advertisementData[CBAdvertisementDataLocalNameKey] as? String else {
print("[centralManager:didDiscover peripheral:advertisementData] peripheral name is unknown")
return
}
if perfName.contains("V.ALRT") {
peripheral.delegate = self
perfs.append(peripheral)
if connectedButtonUUID == perfName {
connect(peripheral: peripheral)
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("[central:didConnect peripheral:] CB BLE hardware did connect")
handleDidConnect(toPeripheral: peripheral)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("[central:didFailToConnect error:] CB BLE peripheral did failed to connect with error: \(String(describing: error))")
connectedPerf = nil
isButtonReady = false
connectedButtonUUID = nil
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("[central:didDisconnectPeripheral peripheral:] CB BLE peripheral did disconnect")
connectedPerf = nil
isButtonReady = false
connectedButtonUUID = nil
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("[central:willRestoreState dict:] CB BLE hardware will restore state")
print("\(dict)")
guard let ps = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
print("[central:willRestoreState dict:] No perfs to restore")
return
}
print("[central:willRestoreState dict:] Will restore perfs")
perfs = ps
print("[central:willRestoreState dict:] Attempt to reconnect to V.BTTN")
print("[central:willRestoreState dict:] perfs \(perfs)")
for p in perfs {
if p.name == connectedButtonUUID {
print("[central:willRestoreState dict:] Connect to perf \(p)")
handleDidConnect(toPeripheral: p)
break
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else {
print("[peripheral:didDiscoverServices error:] CB BLE peripheral unable to discover services")
return
}
print("[peripheral:didDiscoverServices error:] BLE peripheral did discover services")
for s in services {
print("[peripheral:didDiscoverServices error:] CB BLE Service \(s.description)")
peripheral.discoverCharacteristics(nil, for: s)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("[peripheral:didDiscoverCharacteristicsFor service:] CB BLE did discover characteristics for service \(service.uuid.description)")
if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {
guard let characteristics = service.characteristics else {
return
}
for aChar in characteristics {
// write the verification key
if aChar.uuid.isEqual(CBUUID(string: BLE_VERIFICATION_SERVICE_UUID)) {
self.writeVerificationKey(forPeripheral: peripheral, characteristic: aChar)
self.enable(peripheral: peripheral)
break
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
//NOTE: This code has been omitted as there is little need to see this
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("[peripheral:didWriteValueFor characteristic:] characteristic: \(characteristic.uuid)")
if let e = error {
print("[peripheral:didWriteValueFor characteristic:] error: \(e)")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if !characteristic.isNotifying {
print("[peripheral:didUpdateNotificationStateFor:][Characteristic is not notifiying so cancel the connection]")
manager?.cancelPeripheralConnection(peripheral)
}
}
//MARK: - Helpers
fileprivate func writeVerificationKey(forPeripheral peripheral: CBPeripheral, characteristic: CBCharacteristic) {
print("[peripheral:didDiscoverCharacteristicsFor service:] Write verification key")
let data = NSData(bytes: [0x80,0xBE,0xF5,0xAC,0xFF] as [UInt8], length: 5)
peripheral.writeValue(data as Data, for: characteristic, type: CBCharacteristicWriteType.withResponse)
}
fileprivate func handleDidConnect(toPeripheral peripheral: CBPeripheral) {
if let n = peripheral.name {
connectedButtonUUID = n
}
connectedPerf = peripheral
peripheral.delegate = self
peripheral.discoverServices(nil)
isButtonReady = true
}
fileprivate func enable(peripheral: CBPeripheral) {
guard let services = peripheral.services else {
return
}
for service: CBService in services {
if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {
guard let characteristics = service.characteristics else {
return
}
for aChar in characteristics {
if aChar.uuid.isEqual(CBUUID(string: BLE_KEYPRESS_DETECTION_UUID)) {
// enable button
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable short press")
let data = NSData(bytes: [0x02] as [UInt8], length: 1)
peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);
} else if aChar.uuid.isEqual(CBUUID(string: BLE_SILENT_NORMAL_MODE)) {
// enable button
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable normal mode")
let data = NSData(bytes: [0x00] as [UInt8], length: 1)
peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);
} else if aChar.uuid.isEqual(CBUUID(string: BLE_FALL_KEYPRESS_DETECTION_UUID)) {
// enable fall detection
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable fall detection")
peripheral.setNotifyValue(true, for: aChar)
}
}
}
}
checkBatteryLevel()
}
func disconnectVbttn() {
guard let peripheral = connectedPerf else {
return
}
setVbttnToStealthMode()
manager?.cancelPeripheralConnection(peripheral)
isButtonReady = false
}
fileprivate func compareCBUUID(uuid1: CBUUID, uuid2: CBUUID) -> Bool {
if (uuid1.data as NSData).isEqual(to: uuid2.data) {
return true
}
return false
}
func stopScanning() {
manager?.stopScan()
}
}

Updating code to Swift 3

I am a beginner to programming with swift and programming in general. I am currently working on an app which allows one phone to send a message (peripheral) to another which will receive the message (central). I have taken most of the code from an app built using swift 2 and have tweaked it slightly to remove any errors. As a result, my code is a jumble of swift 2/3 (or so I have been told). I am not sure how to update this code fully to swift 3 which I need someones help to do. I believe it is the IBActions and delegates that are outdated but I am completely clueless on how to amend it.
I have tried running the app and it runs successfully but when I press any of the buttons nothing happens as far as I can tell. I have a basic understanding of what my code does and what each function is there for but I find myself stuck on what to do for it to work. If anyone has experience working with Bluetooth via swift and can see what I am doing wrong or perhaps if I haven't included something important. I am led to believe the issue is that all the code has not been updated to swift 3 but I can't be sure. I have read the Apple documentation for Core bluetooth.
PS: I know this question is very vague as I am unsure on what part of my code I need to fix so it is very difficult to make it specific to a single issue. Sorry!
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBPeripheralManagerDelegate, CBCentralManagerDelegate, CBPeripheralDelegate {
let messageUUID = CBUUID(string: "053D6600-821E-46A7-AC25-43A81D948E8B")
let inputUUID = CBUUID(string: "49A79D26-5323-4374-81EA-29B099AF85C8")
let otherUUID = CBUUID(string: "053D6600-821E-46A7-AC25-43A81D948E87")
var peripheralManager: CBPeripheralManager!
var characteritic: CBMutableCharacteristic!
var getDataPeripheralCharacteristic: CBMutableCharacteristic!
var service: CBMutableService!
var outputData:String = ""
var centralManager: CBCentralManager!
var connectingPeripheral: CBPeripheral?
var centralWriteCharacteristic: CBCharacteristic!
#IBOutlet var inputLabel: UITextField!
#IBOutlet weak var outputLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func centralMode(_ sender: UIButton) {
centralManager = CBCentralManager(delegate: self, queue: nil)
peripheralManager = nil
}
#IBAction func peripheralMode(_ sender: UIButton) {
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
centralManager = nil
}
#IBAction func sendData(_ sender: UIButton) {
if (peripheralManager != nil) {
let passed = peripheralManager.updateValue(inputLabel.text!.data(using: String.Encoding.utf8)!, for: characteritic!, onSubscribedCentrals: nil)
if passed == false {
print("error couldn't send data")
}
}
if (centralManager != nil) {
if (connectingPeripheral != nil) {
connectingPeripheral?.writeValue(inputLabel.text!.data(using: String.Encoding.utf8)!, for: centralWriteCharacteristic, type: CBCharacteristicWriteType.withResponse)
}
}
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if (peripheral.state == .poweredOn) {
characteritic = CBMutableCharacteristic(type: messageUUID, properties: CBCharacteristicProperties.notify, value: nil, permissions: CBAttributePermissions.readable)
getDataPeripheralCharacteristic = CBMutableCharacteristic(type: inputUUID, properties:
CBCharacteristicProperties.write, value: nil, permissions: CBAttributePermissions.writeable)
service = CBMutableService(type: otherUUID, primary: true)
service.characteristics = [characteritic!, getDataPeripheralCharacteristic!]
peripheralManager!.add(service)
}
}
func peripheralManager(peripheral: CBPeripheralManager, didAddService service: CBService, error: NSError?) {
peripheral.stopAdvertising()
let identifier = Bundle.main.bundleIdentifier!
let manufacturerData = identifier.data(using: String.Encoding.utf8, allowLossyConversion: false)
let dataToBeAdvertised:[String: Any] = [
CBAdvertisementDataLocalNameKey : "Sample peripheral",
CBAdvertisementDataManufacturerDataKey : manufacturerData as Any,
CBAdvertisementDataServiceUUIDsKey : [messageUUID, inputUUID],
]
peripheralManager.startAdvertising(dataToBeAdvertised)
}
func peripheralManager(peripheral: CBPeripheralManager, didReceiveWriteRequests requests: [CBATTRequest]) {
for request in requests {
if (request.characteristic.uuid.uuidString == inputUUID.uuidString) {
outputData = String(data: request.value!, encoding: String.Encoding.utf8)!
outputLabel.text = outputData
}
}
}
func peripheralManager(peripheral: CBPeripheralManager, didReceiveReadRequest request: CBATTRequest) {
}
func peripheralManager(peripheral: CBPeripheralManager, central: CBCentral, didSubscribeToCharacteristic characteristic: CBCharacteristic) {
outputLabel.text = "Press send message"
print("Now connected")
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
centralManager.scanForPeripherals(withServices: nil, options: nil)
//search for peripheral device
}
}
//when peripheral is discovered, this method is called
private func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
self.centralManager.stopScan();
self.connectingPeripheral = peripheral;
peripheral.delegate = self;
centralManager.connect(peripheral, options: nil)
}
//if connection request from central is succesfull, this method is called
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
peripheral.delegate = self;
peripheral.discoverServices(nil)
}
//if central discovers services, this method is called
private func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
for service: CBService in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
}
}
private func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
if (service.uuid.uuidString == otherUUID.uuidString) {
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
if characteristic.uuid.uuidString == messageUUID.uuidString {
peripheral.setNotifyValue(true, for: characteristic)
} else if characteristic.uuid.uuidString == inputUUID.uuidString {
centralWriteCharacteristic = characteristic
}
}
}
}
private func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
if error != nil {
print("Error reading characteristics");
}
if (characteristic.uuid == messageUUID) {
outputData = String(data: characteristic.value!, encoding: String.Encoding.utf8)!
print(outputData)
outputLabel.text = outputData
}
}
}
If the issue is because of swift 2/3 mismatch. Try upgrading your code to swift3.
Go to Xcode-edit-convert- to current swift syntax.
This will lead to few build errors, identify them and fix them.
Make your sure if you have any pod or cart file , do not forget to upgrade them as well.
Hopefully this should resolve your issue.

Microchip RN4020 -> MLDP mode iOS sample code

It has been now several years that Microchip has released the RN4020 BT LE chip with the private MLDP profile. However, as of yet there is STILL NO PUBLICLY AVAILABLE iOS sample source code available, despite them having an iOS App in the Apple App Store. Has anyone any working code and willing to share/post it?
Thanks!
Tim
I have some working code. I'll give some snippets here. In a first ViewController which conforms to the CBCentralManagerDelegate we have:
var cbc : CBCentralManager? = nil
override func viewDidLoad() {
super.viewDidLoad()
cbc = CBCentralManager(delegate: self, queue: nil)
}
touching a button starts scanning for peripherals
#IBAction func scan(_ sender: Any) {
cbc?.scanForPeripherals(withServices: nil, options: nil)
}
for each peripheral found the following delegate member will be called
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// store peripherals here to let select user one
NSLog("name=%#", peripheral.name ?? "unnamed")
}
we store the peripherals in a dictionary and present its names to the user using a table view. If the user selects a peripheral we try to connect to that
#IBAction func connect(_ sender: Any) {
// selectedPeripheral set by selection from the table view
cbc?.connect(selectedPeripheral!, options: nil)
}
successfull connection will result in a call to the following delegate method:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
performSegue(withIdentifier: "ConnectPeriph", sender: self)
}
which leads to a second ViewController responsible for the connected state. This ViewController conforms to the CBPeripheralDelegate protocol and declares the following variables:
var periph : CBPeripheral! // selected peripheral
var dataChar : CBCharacteristic? // characteristic for data transfer
let mchpPrivateService : CBUUID = CBUUID(string: "00035B03-58E6-07DD-021A-08123A000300")
let mchpDataPrivateChar : CBUUID = CBUUID(string: "00035B03-58E6-07DD-021A-08123A000301")
first action after connection is to discover the services which the peripheral is offering:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
periph.delegate = self
periph.discoverServices(nil)
}
this results in calls to this delegate method:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let e = error {
NSLog("Error %#", e.localizedDescription)
}
else if let services = peripheral.services {
for s in services {
NSLog("Service=%#", s.uuid.uuidString)
if s.uuid.isEqual(mchpPrivateService) {
peripheral.discoverCharacteristics(nil, for: s)
}
}
}
}
which in turn results in discovering characteristics:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
NSLog("characteristics for service %#", service.uuid.uuidString)
if let characteristics = service.characteristics {
for c in characteristics {
if c.uuid.isEqual(mchpDataPrivateChar) {
peripheral.setNotifyValue(true, for: c)
dataChar = c
}
}
}
}
the only characteristic we are interested in is that with the uuid mchpDataPrivateChar. The request for notification results in calls to:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
NSLog("update value for %#", characteristic.uuid)
if let d = characteristic.value {
var s : String = String()
for b in d {
s.append(Character(UnicodeScalar(b)))
}
NSLog("received \(d.count) bytes: \(s)")
}
}
which completes the receiver on the iOS side. Sending bytes is done via:
#IBAction func sendClicked(_ sender: Any) {
if let d = dataChar, let s=sendEdit.text {
let buffer : [UInt8] = Array(s.utf8)
let data : Data = Data(buffer)
periph.writeValue(data, for: d, type: .withResponse)
}
}

Swift CoreBluetooth: Should CentralManager run in a separate thread?

For now I run everything in main thread, so far I noticed only once out of many times that UI gets a bit laggy.
I wonder what is the general practice of utilizint CoreBluetooth library regarding concurrency?
Could you provide some examples what exactly should be run in other queue, if anything?
My usage of bluetooth:
I scan for two peripheral devices, control them by sending approapriate value as CBPeripheralManager in order to make them start sending data from IMU (50Hz/100Hz depending on value).
I synchronize and normalize data from tags and write them into file using streamer.
After transmission is done, I send the data file manually by trigerring relevant action from button.
My code
class BluetoothTagController: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, CBPeripheralManagerDelegate{
static let sharedInstance = BluetoothTagController()
var transferCharacteristic:CBMutableCharacteristic!
var centralManager : CBCentralManager!
var sensorTagPeripheral : CBPeripheral!
var synchronizer:DataSynchronizer!
var sensorTagPeripheralArray : [CBPeripheral] = []
var peripheralManager: CBPeripheralManager!
var bluetoothIsON:Bool = false
var tag1Updating:Bool = false
var tag2Updating:Bool = false
var tag1Changed:Bool = false
var tag2Changed:Bool = false
var tagsIds:[String] = []
var peripheralCounter:Int = 0
var peripheralCounter2:Int = 0
var writeCounter:Int = 0
var timerSet:Bool = false
var haveBeenStarted:Bool = false
override init()
{
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
self.synchronizer = DataSynchronizer(frequency: 1)
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
print("WATCH OUT")
// print(service)
}
func setHaveBeenStarted( haveBeen: Bool) {
haveBeenStarted = haveBeen
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print("subscription started")
var intVal: NSInteger = 0
if haveBeenStarted == true {
intVal = 2
}
let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
var didSend:Bool = self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)
print(didSend)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state != .poweredOn
{
print("no power")
self.bluetoothIsON = false
return
}
self.bluetoothIsON = true
print("powered on")
let serviceCBUUID = CBUUID(string: "5DC90000-8F79-462B-98D7-C1F8C766FA47")
let transferService:CBMutableService = CBMutableService(type: serviceCBUUID, primary: true)
let characteristicBUUID = CBUUID(string: "5DC90001-8F79-462B-98D7-C1F8C766FA47")
var intVal: NSInteger = 2
let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
let transferCharacteristic = CBMutableCharacteristic(type: characteristicBUUID, properties: .notify, value: nil, permissions: .readable)
self.transferCharacteristic = transferCharacteristic
transferService.characteristics = [transferCharacteristic as CBCharacteristic]
self.peripheralManager.add(transferService)
self.peripheralManager.startAdvertising(nil)
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
print("didReceiveReadRequest")
//
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
print("Unsubscribed")
// var intVal: NSInteger = 0
// let valueData:Data = Data(buffer: UnsafeBufferPointer(start: &intVal, count: 1))
// self.peripheralManager.updateValue(valueData, for: self.transferCharacteristic, onSubscribedCentrals: nil)
}
/******* CBCentralManagerDelegate *******/
// Check status of BLE hardware
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
// Scan for peripherals if BLE is turned on
central.scanForPeripherals(withServices: nil, options: nil)
}
else {
// Can have different conditions for all states if needed - show generic alert for now
}
}
// Check out the discovered peripherals to find Sensor Tag
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("array2 contains" + "\(self.sensorTagPeripheralArray.count)")
if SensorTag.sensorTagFound(advertisementData) == true {
// Update Status Label'
self.sensorTagPeripheral = peripheral
self.sensorTagPeripheral.delegate = self
self.centralManager.connect(peripheral, options: nil)
if !self.sensorTagPeripheralArray.contains(peripheral)
{
self.sensorTagPeripheralArray.append(peripheral)
self.tagsIds.append("\(peripheral.identifier)")
// self.centralManager.connectPeripheral(peripheral, options: nil)
}
else {
//showAlertWithText(header: "Warning", message: "SensorTag Not Found")
}
}
}
// Discover services of the peripheral
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("connected " + "\(peripheral.identifier)")
print("array contains" + "\(self.sensorTagPeripheralArray.count)")
numberOfTagsSending = numberOfTagsSending + 1
peripheral.discoverServices(nil)
}
// If disconnected, start searching again
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
// print("error")
// print(error)
// self.sensorTagPeripheralArray.arrayRemovingObject(peripheral)
// print(sensorTagPeripheralArray)
numberOfTagsSending = numberOfTagsSending - 1
print("removed")
synchronizer.alreadySynced = false
central.scanForPeripherals(withServices: nil, options: nil)
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("ciekawe")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("looking for p services")
print("discovered services " + "\(peripheral.identifier)")
for service in peripheral.services! {
let thisService = service as CBService
if SensorTag.validService(thisService) {
// Discover characteristics of all valid services
peripheral.discoverCharacteristics(nil, for: thisService)
}
}
}
// Enable notification and sensor for each characteristic of valid service
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
// print("discovered characteristic " + "\(peripheral.identifier)")
var enableValue = 1
let enablyBytes = Data(buffer: UnsafeBufferPointer(start: &enableValue, count: 1))
// print("\n")
for charateristic in service.characteristics! {
print(charateristic.uuid)
let thisCharacteristic = charateristic as CBCharacteristic
if SensorTag.validDataCharacteristic(thisCharacteristic) {
// Enable Sensor Notification
print( "valid char")
// print(thisCharacteristic)
peripheral.setNotifyValue(true, for: thisCharacteristic)
if thisCharacteristic.uuid == MagnetometerDataUUID{
peripheral.readValue(for: thisCharacteristic)
}
print("after notify set")
// print(self.sensorTagPeripheral.services)
}
if SensorTag.validConfigCharacteristic(thisCharacteristic) {
// Enable Sensor
print("more valid")
// print(thisCharacteristic)
// for peripheral in self.sensorTagPeripheralArray{
peripheral.writeValue(enablyBytes, for: thisCharacteristic, type: CBCharacteristicWriteType.withResponse)
}
}
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print(error)
}
// var streamerTag1 = MyStreamer(fileString: "tag1.txt")
// var streamerTag2 = MyStreamer(fileString: "tag2.txt")
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print(characteristic.value!)
if "\(peripheral.identifier)" == self.tagsIds[0]
{
switch characteristic.uuid
{
case MagnetometerDataUUID:
tag1Compensator.getTrimRegisterData(characteristic.value!)
case IRTemperatureDataUUID:
tag1Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))
case IMUDataUUID:
synchronizer.fillTagArray(characteristic.value!, tag: .first)
default:
return
}
}
else if (self.tagsIds.count > 1) && ("\(peripheral.identifier)" == self.tagsIds[1])
{
switch characteristic.uuid
{
case MagnetometerDataUUID:
tag2Compensator.getTrimRegisterData(characteristic.value!)
case IRTemperatureDataUUID:
tag2Temperature = Double(UInt16(littleEndian: (characteristic.value! as NSData).bytes.bindMemory(to: UInt16.self, capacity: characteristic.value!.count).pointee))
case IMUDataUUID:
synchronizer.fillTagArray(characteristic.value!, tag: .second)
default:
return
}
}
}
}
I'm always running bluetooth activities on a background thread since there is a certain probability that some of bluetooth API calls are blocking.
I guess that main candidates for moving to background are scanning and discovering methods because that's a place where a real hardware operations are performed.
For my tasks using Grand Central Dispatch is enough.
EDIT: the simplest example of GCD usage:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
DispatchQueue.main.async {
valueLabel.text = peripheral.value.map { String(data: $0, encoding: NSUTF8StringEncoding) }
}
}
Try to create blutoothOperation class , which is subclass of Oeration(NSOperation).
Sample reference for operation classes and there utilisation.
https://developer.apple.com/videos/play/wwdc2015/226/
Download sample project from the above link.

Resources