iOS Corebluetooth: Startadvertising() Error Advertising Static Data

iOS CoreBluetooth: startAdvertising() Error advertising static data

The value for the CBAdvertisementDataServiceUUIDsKey in the dictionary passed to startAdvertising is an array of CBUUID objects, but you are only passing a single CBUUID. Once I changed it to an array your code worked.

func peripheralManager(peripheral: CBPeripheralManager, didAddService service: CBService, error: NSError?) {

if (error != nil) {
print("PerformerUtility.publishServices() returned error: \(error!.localizedDescription)")
print("Providing the reason for failure: \(error!.localizedFailureReason)")
}
else {
peripheralManager?.startAdvertising([CBAdvertisementDataServiceUUIDsKey : [service.UUID]])
}
}

Creating a Static Characteristic with Swift and CoreBluetooth

You can't use the CBCharacteristicProperties.Broadcast property with characteristics you create yourself. From the documentation:

  • CBCharacteristicPropertyBroadcast The characteristic’s value can be broadcast using a characteristic configuration descriptor.

    This property is not allowed for local characteristics published via
    the addService: method of the CBPeripheralManager class. This means
    that you cannot use this property when you initialize a new
    CBMutableCharacteristic object via the
    initWithType:properties:value:permissions: method of the
    CBMutableCharacteristic class.

Ble: Send advertise data to iOS from Android

iOS is very restrictive regarding advertisement data. Both when sending and receiving it, you can only control a small subset of it. Most of it is controlled by iOS itself and — in case of the central manager role — not even forwarded to the app.

The exceptions are the Advertisement Data Retrieval Keys, applicable for the advertisementData parameter of centralManager(_:didDiscover:advertisementData:rssi:).

A more specific example is mentioned in this answer.

Update

Even though one of the keys is for service data, I don't think the data is forwarded to the app. But I might be wrong. I guess you are asking this question because the key CBAdvertisementDataServiceDataKey is not set.

Update 2

I've created a minimal Android and iOS example and got it working without any problem. I don't see no obvious problem in your Android code. So you will need to talk to your iOS colleague...

The service data is "ABC" (or 61 62 63 in hex) and the 16-bit UUID is FF01. The iOS log output is:

2019-09-05 16:39:18.987142+0200 BleScanner[18568:3982403] [Scanner] Advertisement data: FF01: <616263>

Android - MainActivity.kt

package bleadvertiser

import android.bluetooth.BluetoothManager
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

private var peripheral: Peripheral? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onStart() {
super.onStart()
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
peripheral = Peripheral(bluetoothManager.adapter)
peripheral?.startAdvertising()
}
}

Android - Peripheral.kt

package bleadvertiser

import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.os.ParcelUuid
import android.util.Log
import java.util.*

private const val TAG = "Peripheral"

class Peripheral(private val bluetoothAdapter: BluetoothAdapter) {

fun startAdvertising() {
val advertiseSettings = AdvertiseSettings.Builder().build()
val serviceData = "abc".toByteArray(Charsets.UTF_8)
val advertiseData = AdvertiseData.Builder()
.addServiceData(ParcelUuid(SERVICE_UUID), serviceData)
.build()
val advertiser = bluetoothAdapter.bluetoothLeAdvertiser
advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback)
}

private val advertiseCallback = object: AdvertiseCallback() {
override fun onStartFailure(errorCode: Int) {
Log.w(TAG, String.format("Advertisement failure (code %d)", errorCode))
}
override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) {
Log.i(TAG, "Advertisement started")
}
}

companion object {
val SERVICE_UUID: UUID = UUID.fromString("0000ff01-0000-1000-8000-00805F9B34FB")
}
}

iOS - ViewController.swift

import UIKit

class ViewController: UIViewController {

var bleScanner: BleScanner?

override func viewDidLoad() {
super.viewDidLoad()
bleScanner = BleScanner()
}
}

iOS - BleScanner.swift

import Foundation
import CoreBluetooth
import os.log

class BleScanner : NSObject {

static let serviceUUID = CBUUID(string: "FF01")
static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Scanner")

private var centralManager: CBCentralManager!
private var scanningTimer: Timer?

override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}

func startScanning() {
scanningTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(20), repeats: false, block: { (_) in
self.stopScanning()
})
centralManager.scanForPeripherals(withServices: [ BleScanner.serviceUUID ], options: nil)
}

func stopScanning() {
centralManager.stopScan()
}
}

extension BleScanner : CBCentralManagerDelegate {

func centralManagerDidUpdateState(_ central: CBCentralManager) {
if centralManager.state == .poweredOn {
startScanning()
}
}

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
for (key, value) in advertisementData {
if key == CBAdvertisementDataServiceDataKey {
let serviceData = value as! [CBUUID : NSData]
for (uuid, data) in serviceData {
os_log("Advertisement data: %{public}s: %{public}s", log: BleScanner.log, type: .info, uuid.uuidString, data.debugDescription)
}
}
}
}
}

Android Peripheral identifier never same

Android's peripheral mode will change its mac address every time when you call BluetoothLeAdvertiser.startAdvertising(). This is a security measure and you cannot disable it, you can read more in this so question.

On iOS/Mac OS, CoreBluetooth will generate UUID for scanned peripheral from the advertisement data and one of the known factor is the mac address of the peripheral. So if the Android peripheral changes its mac address, you have no way to stop the UUID change on the Mac central.

I suggest you to add some data in the advertisement data of your Android peripheral, which helps you to identify it.

iOS Bluetooth peripheralManagerDidUpdateState never called

The object myPeripheralManager is deallocated as soon as viewDidLoad method returns, as you have only one reference pointing to this object and it goes out of scope.

The solution is to create a property in ViewController which references the instance of CBPeripheralManager:

@interface ViewController()
@property (nonatomic, strong) CBPeripheralManager *myPeripheralManager;
@end

and then initialise the property in viewDidLoad:

self.myPeripheralManager = myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:(id<CBPeripheralManagerDelegate>)self queue:nil options:nil];

You may want to read more about memory management (in particular, Automatic Reference Counting) in Objective-C.



Related Topics



Leave a reply



Submit