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
theaddService:
method of theCBPeripheralManager
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
Uibutton Font Size Isn't Changing
Spritekit Skphysicsjointfixed Odd Behaviour
Why Is Icloud Account/Ckcontainer Not Being Found
How to Pass a Class and Method to Create an Instance of a Class
How Convert a *Positive* Number into an Array of Digits in Swift
Maccatalyst App: How to Close a Window Without Terminating The App
How to Set iOS 13 Glyphs Programmatically
Swift Sprite Kit in App Purchase
Best Way to Structure My Firebase Database
Swift Enumerate Functions. How Does It Work Behind
Swift: Incrementing Label with Delay
Property with '= {Return}()' or '{Return}'
Get Current Url from Browser in MACos
How to Make a Custom Geometryreader in Swiftui
How to Use Multiple Mtlrenderpipelinestates in One Mtlrendercommandencoder