Get advertisement data for BLE in iOS
Unfortunately, iOS does not allow you to access the raw advertisement data. I wrote a blog post demonstrating this. While the post is specifically about iBeacons, it applies to any BLE advertisement.
EDIT: To clarify, you can read the raw manufacturer data bytes or service data bytes of non-iBeacon advertisements. It is only the iBeacon advertisements that have their manufacturer data bytes hidden by CoreLocation
. See here: Obtaining Bluetooth LE scan response data with iOS
The equivalent MacOS CoreLocation methods do allow this, so it is probably an intentional security or power saving restriction on iOS.
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)
}
}
}
}
}
iOS omits manufacturer data from advertisement in background mode
An app simply cannot read raw BLE manufacturer advertisement data when in the background on iOS -- the operating system prohibits it.
Two exceptions to this rule:
iBeacon, which itself is implemented as a specific type of manufacturer advertisement. An app can detect iBeacons in the background on iOS, although only four bytes of readable data (encoded in the major and minor fields) are fully usable. If you can modify your device to send information this way, it will do what you want. However you must use CoreLocation APIs to detect iBeacon, as CoreBluetooth does not allow reading manufacturer data from iBeacon advertisements. If you do use CoreLocation, you cannot use the detections to establish a Bluetooth connection with CoreBluetooth as the two APIs are sandboxed.
Overflow Area advertisements. Backgrounded iOS apps can read these special types of manufacturer advertisements when in the background but only if the screen is turned on. (It is often possible to force the screen on at specific times by sending a local notification.) See my blog post here for more info: http://www.davidgyoungtech.com/2020/05/07/hacking-the-overflow-area
An alternative to detecting manufacturer advertisements is to use BLE Service advertisements with attached data. For this to work, you'd need to define a 16 bit or 128 bit GATT Service UUID and send out an advert with attached data bytes. Eddystone beacon formats work this way, and allow detection in the background on iOS. This is probably the best approach if you can alter the BLE hardware.
Attaching advertising payload for iOS without pairing
Unfortunately, you cannot use CoreBluetooth APIs to attach data to advertisements. On iOS the CBAdvertisementDataServiceDataKey is read-only. While Bluetooth LE allows attaching service data , Apple effectively disallows 3rd party apps from doing this.
You do have a few options:
Encode your data inside a 128-bit service UUID and advertise that. You will need to reserver a byte or two in the UUID to know that it is "your" advertisement, and therefore OK to decode the data from the other byes. This full UUID will only be advertised when your app is in the foreground visible on the screen. Let it go to the background or the screen turn off, and it will no longer advertise in that form. Similarly, receiving iOS devices must also be in the foreground with the screen on. This is because iOS disallows getting background scan results without specifying the matching service UUID up front. And because you are dynamically manipulating some of those bytes, you don't know what it will be.
Do a similar kind of encoding with the 4 byte major and minor fields inside the iBeacon BLE advertisement using CoreLocation. Again, this allows you to transmit only when the app is in the foreground. Receiving, however, can happen to a limited degree in the background (for 5-10 seconds after one of your beacons first appears when combining monitoring and ranging APIs.) The big disadvantage is you only have four bytes to work with.
Advertise data by manipulating the 128-bit background BLE Overflow Area Advertisement. This technique is more advanced, but advertising works in the background. Receiving works in the foreground, and partly in the background -- you can receive if the screen is at least turned on. You can read more about this technique and access free sample code in my blog post herehttp://www.davidgyoungtech.com/2020/05/07/hacking-the-overflow-area.
Accessing raw advertisement data for custom BLE device on iOS 8 using Core Bluetooth
You can do what you want if you change the Raspberry Pi to transmit a non-iBeacon format. CoreBluetooth
only filters out the raw bytes of advertisements if they are iBeacon advertisements. See here: Obtaining Bluetooth LE scan response data with iOS
A simple solution is to change your iBeacon advertisement to an open-source AltBeacon advertisement. CoreLocation
will no longer pick it up, but CoreBluetooth
will.
Here's an example of what you get in the advertisementData NSDictionary
in the CoreBluetooth
centralManager:didDiscoverPeripheral:advertisementData:RSSI:
callback. This example is the result of detecting an AltBeacon advertisement (an open-source beacon standard), with identifiers 2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6 1 2:
{
kCBAdvDataIsConnectable = 0;
kCBAdvDataManufacturerData = <1801beac 2f234454 cf6d4a0f adf2f491 1ba9ffa6 00010002 be00>;
}
You can see how to decode the above bytes by looking at the AltBeacon spec here. Note that the above are the actual contents of the NSDictionary
for a detected advertisement on iOS8 that were printed to the console using an NSLog
statement.
Related Topics
iOS Autolayout Vertically Equal Space to Fill Parent View
iOS Autolayout to Center My View Between Two Views
This Certificate Was Signed by an Unknown Authority
How to Add Buttons to Navigation Controller Visible After Segueing
Uicollectionview Scrolling Choppy When Loading Cells
Uiview Atop the Keyboard Similar to Imessage App
Ios7 Safari: Saving to Home-Screen and Persist Token
Reading Long Characteristic Values Using Corebluetooth
Editing Screenshots in Itunes Connect After iOS App Was Approved
How to Calculate the Height of an Nsattributedstring with Given Width in iOS 6
Change the Font Size of Uisearchbar
How to Define the Size of a Collectionview on Rotate
Does Firebase Cloud Messaging Support Voip Pushkit Services
Objc_Sync_Enter/Objc_Sync_Exit Not Working with Dispatch_Queue_Priority_Low
Show Uipickerview Text Field Is Selected, Then Hide After Selected
Uiwebview Random Crash at [Uiviewanimationstate Release]: Message Sent to Deallocated Instance
Printing the View in iOS with Swift
iOS App Crashes, Xcode Says 'Lost Connection to X's Iphone' When Debugging