CoreBluetooth between two iOS apps - ios

I am trying to get started with bluetooth on iOS, in order to make two neighbouring devices communicate using CoreBluetooth.
On one device an app is running playing the role of peripheral and on the other device another app is running playing the role of central.
Here is the relevant code for the two apps.
The peripheral app:
import UIKit
import CoreBluetooth
class ViewController: UIViewController,CBPeripheralManagerDelegate {
let serviceOne_UUID = CBUUID(string:"FB694B90-F49E-4597-8306-171BBA78F846"),
svcOneChrcOne_UUID = CBUUID(string:"EB6727C4-F184-497A-A656-76B0CDAC633A")
var cbPerifMngr:CBPeripheralManager!, mutaSRVC:CBMutableService!,
svcOneCharacOne:CBMutableCharacteristic!
override func viewDidLoad() {
super.viewDidLoad()
cbPerifMngr = CBPeripheralManager(delegate: self, queue: nil)
}
// CBPeripheralManagerDelegate protocol implementation.
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
mutaSRVC = CBMutableService(type: serviceOne_UUID, primary: true)
svcOneCharacOne = CBMutableCharacteristic(type: svcOneChrcOne_UUID,
properties: [.read, .notify],
value: nil, permissions: .readable)
mutaSRVC.characteristics = [svcOneCharacOne]
cbPerifMngr?.add(mutaSRVC)
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
if error != nil {
print("Error in \(#function) :\n\(error!)")
} else {
cbPerifMngr.startAdvertising([CBAdvertisementDataServiceUUIDsKey:service.uuid])
}
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
if error != nil {
print("Error in \(#function) :\n\(error!)")
}
}
}
The central app:
import UIKit
import CoreBluetooth
class ViewController: UIViewController,CBCentralManagerDelegate {
var cbCenterMngr:CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
cbCenterMngr = CBCentralManager(delegate: self, queue: nil)
}
// CBCentralManagerDelegate protocol implementation.
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print(#function)
if central.state == .poweredOn {
print("\(#function) poweredOn")
central.scanForPeripherals(withServices: [CBUUID(string:"FB694B90-F49E-4597-8306-171BBA78F846")],
options: nil)
}
}
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(#function)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print(#function)
}
}
At this point, when I run both apps at the same time, I do not see anything happening showing that some kind of connection is taking place. Can anybody tell me if I need to look in a particular place or if the code above is simply wrong and nothing will happen? In which case some advice or hint would be highly appreciated.
P.S.
After trying again, I noticed this error in the logging console of the peripheral app:
peripheralManager(_:didAdd:error:)
peripheralManagerDidStartAdvertising(_:error:)
Error in peripheralManagerDidStartAdvertising(_:error:) :
Error Domain=CBErrorDomain Code=1 "One or more parameters were invalid." UserInfo={NSLocalizedDescription=One or more parameters were invalid.}

Related

Can't connect to AirPods via BLE

I am trying to connect automatically to my AirPods with a simple app using BLE. I get the name of the device and the status to "connecting", but for some reason I can't connect to it. Function 'didConnect peripheral' is never triggered.
I tried all different approaches from tutorials and from other posts, tried to store the peripheral data in an array to keep the reference but nothing seem to work.
Is there any step which I can get some extra info between 'didDiscover' and 'didConnect'?
Working in XCode 9.2, using Swift 4 and iOS 11.2 on iPhone.
Here's my code:
let deviceName = "AirPods de Roger"
var isConnected = false
var manager: CBCentralManager!
var peripheralBLE: CBPeripheral?
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch manager.state {
case.poweredOff:
print("BLE service is powered off")
case.poweredOn:
print("BLE service is powered on and scanning")
manager.scanForPeripherals(withServices: nil, options: nil)
default:
print("BLE service in another state")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if peripheral.name == deviceName && isConnected == false {
self.manager.stopScan()
self.peripheralBLE = peripheral
self.peripheralBLE?.delegate = self
manager.connect(peripheral, options: nil)
isConnected = true
print("\(peripheral.name) pre-connected")
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
lblConnected.isHidden = false
print("AirPods Connected")
peripheral.discoverServices(nil)
}
This one is my current implementation:
import UIKit
import CoreBluetooth
var manager: CBCentralManager!
var peripheralBLE: CBPeripheral!
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
let deviceName = "AirPods de Iván"
var isConnected = false
#IBOutlet weak var Label: UILabel!
#IBAction func Click(_ sender: UIButton) {
self.connect()
}
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func connect() {
manager.connect(peripheralBLE, options: nil)
print("connect")
self.updateLabelStatus()
}
func disconnect() {
manager.cancelPeripheralConnection(peripheralBLE!)
print("disconnect")
self.updateLabelStatus()
}
func updateLabelStatus() {
switch peripheralBLE.state {
case.connected:
Label.text = "connected"
case.disconnected:
Label.text = "disconnected"
case.connecting:
Label.text = "connecting"
case.disconnecting:
Label.text = "disconnecting"
default:
Label.text = "label"
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch manager.state {
case.poweredOff:
print("BLE service is powered off")
case.poweredOn:
print("BLE service is powered on and scanning")
manager.scanForPeripherals(withServices: nil, options: nil)
default:
print("BLE service in another state")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if peripheral.name == deviceName && isConnected == false {
print("found AirPods \(peripheral)")
peripheralBLE = peripheral
peripheralBLE!.delegate = self
manager.stopScan()
self.updateLabelStatus()
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("AirPods Connected")
peripheral.discoverServices(nil)
self.updateLabelStatus()
}
func centralManager(_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?) {
print("AirPods Connect error")
print(error)
self.updateLabelStatus()
}
}
I am finding the device but when I try to connect, nothing happens
BLE service is powered on and scanning
found AirPods <CBPeripheral: 0x1c4103f00, identifier = 0E6FCF72-B86E-FB10-DD62-4A575BAD0ECC, name = AirPods de Iván, state = disconnected>
connect
I can connect other devices with this code
I can connect the airpods with my mac without problems with a similar code
I can't connect airpods with my iphone :S
Something strange happens here I am almost sure that the code is right
Since the AirPods advertise with kCBAdvDataIsConnectable = 0, they're not supposed to be connected via BLE.

Bluetooth didConnect or didFailToConnect not called Xcode 9.2 with iOS 11.1.2

I am having the problem with Xcode 9.2 and iOS 11.1.2,"didDiscover" is working fine and my peripheral is saved in an array before I call connect, but "didConnect" or "didFailToConnect" not called, so the peripheral state will stay at "connecting"...
Please help
var manager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
//manager = CBCentralManager (delegate: self, queue: DispatchQueue.main)
//manager = CBCentralManager.init(delegate: self, queue: nil, options:[:])
//manager.delegate = self
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard let name = peripheral.name else { return }
print("BR peripheral.name = \(name), rssi = \(RSSI), adertisementData = \(advertisementData)")
if (peripheral.name?.hasPrefix("testBT"))! {
peripheralArray.append(peripheral)
manager.connect(peripheralArray.first!, options: [:])
print("Connecting to peripheral \(peripheral)")
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("BT connected!")
manager.stopScan()
peripheral.delegate = self
peripheral.discoverServices(nil)
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("BT disconnected!")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("BT failed!")
}
Try making variable global:
var myPeripheral:CBPeripheral?
Than, in your code edit it:
if (peripheral.name?.hasPrefix("testBT"))! {
myPeripheral = peripheral
//HERE WRITE DELEGATE, NOT IN didConnect
myPeripheral!.delegate = self
//peripheralArray.append(myPeripheral)
manager.connect(myPeripheral!, options: nil)
print("Connecting to peripheral \(peripheral)")
}
Now I think it should work. I would also start scanning on centralManagerDidUpdateState to make sure bluetooth is working, smth like:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state{
case .poweredOn:
startScan() // own method where i start scanning
case .poweredOff:
print("blth powered off")
case .unsupported:
print("bltyh noit supported")
}
}
etc. there is more states, you can check it in documentation. The centralManagerDidUpdateState method is beeing called after initializing in vievDidLoad your CBCentralManager var (manager = CBCentralManager(delegate: self, queue: nil))
Hope it helps
BTW, you are making an array to store there connected peripherals. Just have in mind that whenever you return from the controller where you store that array and go back to it that array will be empty again. That's why if you need your bluetooth class working globally I would suggest you to make a BluetoothService class that would have a Singleton implemented and you could access any state anywhere in your project

Receive string data via Bluetooth

I'm creating a simple BLE app that communicates with a single peripheral. The phone acts as the central. I have an iPad which I'm using as the peripheral for testing. It has the app LightBlue installed to simulate a peripheral. The peripheral is supposed to send a string of data in this format.
TEM:25.11 | HUM:70 | PM10:43 | PM25:32
So I created a blank virtual peripheral in LightBlue with one service.
Below is my code for Bluetooth connectivity handling.
import UIKit
import CoreBluetooth
class ViewController: UIViewController {
fileprivate let serviceUUID = CBUUID(string: "19B10010-E8F2-537E-4F6C-D104768A1214")
fileprivate let characteristicUUID = CBUUID(string: "19B10011-E8F2-537E-4F6C-D104768A1214")
fileprivate var manager: CBCentralManager!
fileprivate var peripheral: CBPeripheral!
fileprivate var characteristic: CBCharacteristic!
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopScan()
}
fileprivate func startScan() {
manager.scanForPeripherals(withServices: [serviceUUID], options: nil)
}
fileprivate func stopScan() {
manager.stopScan()
}
fileprivate func disconnectFromDevice() {
guard let peripheral = peripheral else { return }
manager.cancelPeripheralConnection(peripheral)
}
fileprivate func restoreCentralManager() {
manager.delegate = self
}
}
// MARK: - CBCentralManagerDelegate
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unsupported:
print("Unsupported")
case .unauthorized:
print("Unauthorized")
case .poweredOn:
print("Powered On")
startScan()
case .resetting:
print("Resetting")
case .poweredOff:
print("Powered Off")
case .unknown:
print("Unknown")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Discovered \(String(describing: peripheral.name)) at \(RSSI)")
if peripheral.name == nil || peripheral.name == "" {
return
}
if self.peripheral == nil || self.peripheral.state == .disconnected {
stopScan()
self.peripheral = peripheral
central.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
self.peripheral = nil
central.scanForPeripherals(withServices: nil, options: nil)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
self.peripheral = nil
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
}
}
// MARK: - CBPeripheralDelegate
extension ViewController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
print("No. of services: \(services.count)")
for service in services {
print(service.uuid)
if service.uuid == serviceUUID {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print("characteristic: \(characteristic.uuid)")
if characteristic.uuid == characteristicUUID {
self.characteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
peripheral.readValue(for: characteristic)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print(error)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == characteristicUUID {
print("Got reply from: \(characteristic.uuid)")
print(characteristic.value)
if let data = characteristic.value, let string = String(data: data, encoding: String.Encoding.utf8) {
print(string)
} else {
print("No response!")
}
}
}
}
The discovering and connecting part works just fine. The problem is I don't receive that data string from the peripheral.
The method peripheral(_:didUpdateValueFor:error:) does get fired. I get the Got reply from: 19B10011-E8F2-537E-4F6C-D104768A1214 output in the console. However when I tried to see if there's any data by printing out the characteristic.value, it returns nil.
Not sure if it's something wrong with my code. Or I've configured the peripheral on LightBlue wrong. Does LightBlue send out data automatically? I don't see any Send button or anything anywhere either.
I uploaded a demo project here as well.
Your LightBlue configuration showing that you can only write value on that particular characteristic You have to make this read also

Swift 3 Core Bluetooth not discovering services

I'm trying to discover services from a BLE device I've connected to from my iOS app, but when my didDiscoverServices function gets called, the peripheral services array is empty.
The app connects to the device, runs peripheral.discoverServices(nil) and then the didDiscoverServices function gets called, but there are no services returned.
I've read a number of bluetooth related answers and other examples online and as best I can tell, my code is no different to what it should be, except it is not working. I feel like I must've missed something somewhere but I'm not sure what.
I've added my console log below for what I get when running the code, and the bluetooth code at the bottom for reference.
Bluetooth initialised
BLE is powered on
Optional("Nordic_Blinky") found at -72
Scanning stopped
Connect request sent
Connected to <CBPeripheral: 0x1c0301a70, identifier = 0887CF7F-98C8-3FCF-2D10-873FFFFB2B65, name = Nordic_Blinky, state = connected>
Discovering services
Services -- Optional([]) and Error -- nil
My BluetoothHandler class code is below
class BluetoothHandler : NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
// MARK: Properties
var manager: CBCentralManager!
var targetPeripheral: CBPeripheral!
// MARK: Shared Instance
static let sharedInstance = BluetoothHandler()
private override init() {
super.init()
self.startManager()
print("Bluetooth initialised")
}
// MARK: Functions
func startManager() {
manager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var consoleMessage = ""
switch (central.state) {
case.poweredOn:
consoleMessage = "BLE is powered on"
manager.scanForPeripherals(withServices: nil, options: nil)
case.poweredOff:
consoleMessage = "BLE is powered off"
case.resetting:
consoleMessage = "BLE Resetting"
case.unknown:
consoleMessage = "BLE state unknown"
case.unsupported:
consoleMessage = "Device not supported by BLE"
case.unauthorized:
consoleMessage = "BLE not authorised"
}
print("\(consoleMessage)")
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if (peripheral.name == "Nordic_Blinky") {
print("\(String(describing: peripheral.name)) found at \(RSSI)")
self.stopScan()
if targetPeripheral != peripheral {
targetPeripheral = peripheral
targetPeripheral!.delegate = self
manager.connect(targetPeripheral, options: nil)
print("Connect request sent")
}
}
else if (peripheral.name != nil) {
print("\(String(describing: peripheral.name))")
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to \(peripheral)")
peripheral.delegate = self
peripheral.discoverServices(nil)
print("Discovering services")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Any) {
print("Connection failed", error)
}
func centralManager(_ central: CBCentralManager, didDisconnect peripheral: CBPeripheral) {
if self.targetPeripheral != nil {
self.targetPeripheral!.delegate = nil
self.targetPeripheral = nil
}
print("Connection disconnected")
self.startManager()
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("Services -- \(peripheral.services) and Error -- \(error)")
if let services = peripheral.services {
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
Try this: swift 3
If your peripheral(BLE device) have services then print in log.
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services as [CBService]!{
for service in services{
peripheral.discoverCharacteristics(nil, for: service)
}
}
print("==>",peripheral.services!)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for newChar: CBCharacteristic in service.characteristics!{
print(newChar)
}
}

Beacon is not discovering iOS

I am trying to discover the ibeacon. i am running app on iPhone 4s. device bluetooth is turned on. beacon is also discovering in other iOS apps.
When i am searching for services following delegate "centralManagerDidUpdateState" gets called and State is "On" after this nothing happens. It is expected that "didDiscoverPeripheral" should get called but nothing happens. What wrong i am doing here?
Hi,
import UIKit
import FBSDKLoginKit
import SwiftQRCode
import CoreBluetooth
#objc
class DashboardViewController: UIViewController {
var myCentralManager:CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myCentralManager = CBCentralManager(delegate: self, queue: nil)
// let options = [CBCentralManagerScanOptionAllowDuplicatesKey:true]
myCentralManager.scanForPeripheralsWithServices(nil, options: nil)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.hidden = true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
// MARK: CBCentralManagerDelegate
extension DashboardViewController: CBCentralManagerDelegate {
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
peripheral.delegate = self
// if peripheral.state == CBPeripheralState.Disconnected {
//
// central.connectPeripheral(peripheral, options: nil)
// }
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
}
func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
}
func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
}
}
// MARK: CBCentralManagerDelegate
extension DashboardViewController: CBPeripheralDelegate {
func centralManagerDidUpdateState(central: CBCentralManager) {
switch central.state{
case CBCentralManagerState.PoweredOn:
print("On.")
break
case CBCentralManagerState.PoweredOff:
print("Off.")
break
case CBCentralManagerState.Resetting:
print("Resetting.")
break
case CBCentralManagerState.Unauthorized:
print("Unauthorized.")
break
case CBCentralManagerState.Unknown:
print("Unknown.")
break
case CBCentralManagerState.Unsupported:
print("Unsupported.")
break
}
}
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
for service in peripheral.services!{
peripheral.discoverCharacteristics(nil, forService: service)
}
}
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
for charactristics in service.characteristics!{
peripheral.setNotifyValue(true, forCharacteristic: charactristics)
}
}
func peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
if characteristic.isNotifying {
print("Notifying...")
}
}
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
print(characteristic.value)
}
}
It is not possible to detect iBeacons with CoreBluetooth APIs and the CBCentralManager class as shown in the code. While iBeacons are bluetooth LE devices, Apple has security blocks that prevent iOS devices from reading the raw advertisement data with the didConnectPeripheral callback.
If you want to detect iBeacons on iOS, you must use the CoreLocation APIs. You can read more about how to do this here.

Resources