0
votes

I am trying to write a simple app that constantly broadcasts a 'beacon' even when the app is not active. I know that using CoreLocation will switch this off when the app is not in use so I was trying to build a solution using Core Bluetooth. The trouble is that I can't get the app to start advertising.

import UIKit
import CoreBluetooth

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralManagerDelegate {

    var centralManager:CBCentralManager = CBCentralManager()
    var peripheralManager:CBPeripheralManager = CBPeripheralManager()
    let uuid:CBUUID = CBUUID(string: "DCEF54A2-31EB-467F-AF8E-350FB641C97B")

    override func viewDidLoad() {
        super.viewDidLoad()
        self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
        self.centralManager.delegate = self
        let advertisingData = [CBAdvertisementDataLocalNameKey:"my-peripheral", CBAdvertisementDataServiceUUIDsKey: uuid]
        peripheralManager.startAdvertising(advertisingData)
        centralManager.scanForPeripheralsWithServices([uuid], options: nil)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func peripheralManagerDidStartAdvertising(peripheral: CBPeripheralManager, error: NSError?) {
        print("started advertising")
        print(peripheral)
    }

    func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
        print("peripheral discovered")
        print("peripheral: \(peripheral)")
        print("RSSI: \(RSSI)")
    }

    func centralManagerDidUpdateState(central: CBCentralManager) {
        print("central state updated")
        print(central.description)
        if central.state == .PoweredOff {
            print("bluetooth is off")
        }
        if central.state == .PoweredOn {
            print("bluetooth is on")
            let advertisingData = [CBAdvertisementDataLocalNameKey:"my-peripheral", CBAdvertisementDataServiceUUIDsKey: uuid]
            let service = CBMutableService(type: uuid, primary: true)
            self.peripheralManager.addService(service)
            peripheralManager.startAdvertising(advertisingData)
            centralManager.scanForPeripheralsWithServices(nil, options: nil)
        }
        if central.state == .Unsupported {
            print("bluetooth is unsupported on this device")
        }
    }

    func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager) {
        print("peripheral state updated")
        print("\(peripheral.description)")
    }

}

I have installed this on two devices, the issue seems to be in the transmission of the advert since peripheralManagerDidStartAdvertising() is never called.

3
You can't start advertising until you have verified the power on state in peripheralManagerDidUpdateStatePaulw11
I could not find the peripheralManagerDidUpdateState delegate method. I am starting advertising in centralManagerDidUpdateState. This was also taking place in viewDidLoad() but I have since commented this out.Mark Tyers
The peripheralManagerDidUpdateState method is in the code you have shown, it just has an empty implementation - you need to check for the power on state before advertising, but more importantly your CBMutableService instance needs to be stored in a property, not a local variable otherwise it will be released once centralManagerDidUpdateState exits; CBPeripheralManager does not retain the CBMutableServicePaulw11

3 Answers

0
votes

If you use this init method the centralManagerDidUpdateState is going to work. You can set the CBCentralManagerScanOptionAllowDuplicatesKey option if you would like to see the beacons continually.

import UIKit
import CoreBluetooth

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralManagerDelegate {

var centralManager:CBCentralManager!
var peripheralManager:CBPeripheralManager = CBPeripheralManager()
let uuid:CBUUID = CBUUID(string: "DCEF54A2-31EB-467F-AF8E-350FB641C97B")

override func viewDidLoad() {
    super.viewDidLoad()
    self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    self.centralManager = CBCentralManager(delegate: self, queue: nil)
    let advertisingData = [CBAdvertisementDataLocalNameKey:"my-peripheral", CBAdvertisementDataServiceUUIDsKey: uuid]
    peripheralManager.startAdvertising(advertisingData)
    centralManager.scanForPeripheralsWithServices([uuid], options: [ CBCentralManagerScanOptionAllowDuplicatesKey : true])
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func peripheralManagerDidStartAdvertising(peripheral: CBPeripheralManager, error: NSError?) {
    print("started advertising")
    print(peripheral)
}


func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
    print("peripheral discovered")
    print("peripheral: \(peripheral)")
    print("advertisement: \(advertisementData)")
    if let data = advertisementData["kCBAdvDataServiceData"] {
        print("found advert data: \(data)")
    }
    print("RSSI: \(RSSI)")
}

func startAdvert(){
    let advertisingData = [CBAdvertisementDataLocalNameKey:"my-peripheral", CBAdvertisementDataServiceUUIDsKey: uuid]
    peripheralManager.startAdvertising(advertisingData)
}

func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
    print("peripheral disconnected")
    print("peripheral: \(peripheral)")
}

func centralManagerDidUpdateState(central: CBCentralManager) {
    print("central state updated")
    print(central.description)
    if central.state == .PoweredOff {
        print("bluetooth is off")
    }
    if central.state == .PoweredOn {
        print("bluetooth is on")
        centralManager.scanForPeripheralsWithServices(nil, options: [ CBCentralManagerScanOptionAllowDuplicatesKey : true])
        startAdvert()
    }
    if central.state == .Unsupported {
        print("bluetooth is unsupported on this device")
    }
}

func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager) {
    print("peripheral state updated")
    print("\(peripheral.description)")
}

}
0
votes

The CBAdvertisementDataServiceUUIDsKey value passed to startAdvertising should be an array of CBUUID objects but you are passing a single CBUUID

let advertisingData = [CBAdvertisementDataLocalNameKey:"my-peripheral", CBAdvertisementDataServiceUUIDsKey: [uuid]]
0
votes

The issue is related to immediate call .startAdvertising() right after .addService() call.

You must call .startAdvertising() ONLY after func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) delegate method is called.