IOS app is a peripheral. After pairing, peripheral can successfully update characteristic value to and indicate to subscribed central via updateValue:forCharacteristic:onSubscribedCentrals:
When I call this method successfully (after pairing), logs show that characteristic had 1 subscribed central, via characteristic.subscribedCentrals.
However, If I use Xcode to stop and start the app again and try to update characteristic value, logs show that characteristic now has 0 subscribed centrals, and central device doesnt do expected behavior so I'm assuming it wasnt received.
How I make sure the central remains subscribed to my services characteristic after reboot/reconnect?
Thanks
I believe that the only way you can do this is to create a bond between the devices. Usually, once the app is killed/restarted, the value of the CCCD (Client Characteristic Configuration Descriptor) is reset and therefore the remote central app needs to re-enable it every time upon connection. However, when the two devices bond, the GATT server stores the information on which CCCDs are enabled, and then upon each subsequent connection those CCCD values are loaded. You can find more information here:-
What does CCCD mean
How to know if the notification/indication of a characteristic is enabled
GATT (Services and Characteristics) - check attribute caching
The ultimate guide to iOS's CoreBluetooth - check Pairing and Bonding
Related
So I am developing an application which interacts with a bluetooth peripheral, and I am running into the issue that there is currently no way to tell if the user rejected the peripheral's pairing request or not.
The current flow which is implemented is:
Scan for peripherals
Connect to peripheral
Discover services
Discover characteristics
(Pairing request is sent here by the peripheral, and the OS presents the pairing permission popup)
Read from characteristics
I would like to present an error state if the user rejects the pairing request, but I do not seem to be able to tell whether this has happened:
There is no error callback executed on the CBCentralManagerDelegate or CBPeripheralDeleagate
All the characteristics are discovered, whether or not paring has been accepted
Reading from/ writing to characteristics fails silently: i.e. the peripheral just doesn't return a value when a characteristic is read from, but there's no affirmative error to let me know that it's because of a permission failure.
According to this article it indicates that there's no direct way to know that the pairing request was cancelled.
According to this answer it looks as if there is not currently a direct way to detect whether a peripheral is bonded in iOS.
Is there any known workaround for this issue?
In my iOS app I am able to successfully connect to BLE peripherals, discover service and subscribe to characteristics so that app gets notified whenever there is a change in characteristic value. All these happens with no issues as long as app runs in background.
But issue occurs when state restoration happens. In centralManager:willRestoreState: method, I am able to retrieve previously connected peripherals using the method retrieveConnectedPeripheralsWithServices:. But nothing happens when I call discoverServices: method on the retrieved peripheral. peripheral:didDiscoverServices: method never gets called. The value of retrieved peripheral's services property is also null. Does iOS not cache the services and characteristics ?
Note: Our BLE peripheral advertises service initially. When the app launches for first time, it reads value from peripheral and writes user specific data to a characteristic. Post writing, peripheral stops advertising services. But firmware engineer claims that even though service is stopped by peripheral, peripheral retrieved from state restoration should have the cached service. Is it true ?
Have you checked the connection state of the peripherals that you have retrieved via retrieveConnectedPeripheralsWithServices:? The CoreBluetooth framework has some quirks that you need to be aware of, one being the behaviour of the retrieveConnectedPeripheralsWithServices: method:
When you obtain the peripherals via the aforementioned method they might only be connected on the system level (iOS) but not within your app (see the Discussion section in the API documentation). Therefore, you still have to call connect on the peripherals before you can use them properly. Here is the corresponding part from the API documentation:
The list of connected peripherals can include those that are connected by other apps and that will need to be connected locally using the connectPeripheral:options: method before they can be used.
Regarding the caching of services: iOS caches any discovered service and corresponding characteristic. There are only to ways to force iOS to update the cache:
a BLE power cycle, e.g., turning off and on BLE in the Settings App or restarting your iOS device
send a Services Changed notification via the GAP service from the peripheral side (requires an active connection)
Note: You can also activate log messages from the Bluetooth Stack via the Bluetooth Configuration Profile. They can be quite helpful when debugging BLE related issues especially with custom hardware involved (even though the logs are a bit cumbersome to use).
I am facing some issue while reading data from bluetooth peripheral. We have a bluetooth device with the following gatt details.
<service uuid="service id" advertise="true">
<description>XXXX service</description>
<characteristic uuid="characteristic id" id="xgatt_data">
<description>Data</description>
<properties write="true" indicate="true" />
<value variable_length="true" length="20" type="user" />
</characteristic>
</service>
What i am doing is
Search for the peripheral who is having "service id"
Connect to the peripheral once found and keeping a strong reference to it.
After connecting setting delegate and searching for all the services
it providing
loop all services and scan for characteristics once we discover
service
after i found desired characteristic i am enabling notification
On button click i am writing data to characteristic
I am able to connect to the device. and i can send commands(data) to peripheral also. Once we send any command to device it will send some data in response. I can see logs at the device, it is sending some data back once it receive any command. But in my iOS device i am not able to read the data by using either notification or normal read functions. What i am missing here?
Notifications and Indiciations are two different things: If you enable indications you won't receive notifications, and if you enable indications then the application sending the indications expects a response to every sent indiciation (from the application layer).
Also your permissions seem not quite right: the xgatt_data characteristic does not necessarily need the "Write" permission, unless you intend to let a client change it's value. It requires the "Read" permission if you want to read data from the notification, other than simply being notified that it was received.
To enable notifications a client characteristic configuration descriptor is required (UUID 00002901-..) within the xgatt_data characteristic. This CCCD requires "Read+Write" permissions to enable notifications/indications (some work without read permission, some dont).
And finally make sure that your device never agrees to an Attribute MTU of less than the size of your notification, it will not be received by the peripheral if it doesnt fit into a single package. If data length extensions are used, dont enable notifications until after DLE was negotiated, or keep the notification value size below your device's original minimum attribute MTU.
I found the issue. Actually issue is with the emulator. And now i can get data update indications and able to write data to BLE with only "read" and "indicate" properties.
I want only trusted devices to connect to my peripheral. And I don't want anyone to be able to discover services and characteristics of my peripheral.
So before connecting to the peripheral I would like to show an alert with a pin code. Is it possible to do it and what is the easiest way?
I couldn't find the answer to this question and tried to implement encrypted characteristic by adding CBAttributePermissionsWriteEncryptionRequired to the permissions:
self.characteristic = [[CBMutableCharacteristic alloc] initWithType:[JUUIDBuilder uuidWith:#"1706"]
properties: CBCharacteristicPropertyWrite
value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
For some reason it didn't help because I'm able to write values from my second device all the time without any security checks. (Documentation for CBAttributePermissionsReadEncryptionRequired says:
...the characteristic is configured to allow only trusted devices to read or subscribe to its value. When a connected, remote central tries to read or subscribe to this characteristic’s value, Core Bluetooth tries to pair your local peripheral with the central to create a secure connection.
which doesn't make sense to me. What is "trusted devices" here?
Can anyone help me? What is the best practice to allow connections only from trusted devices with pin code confirmation?
You cannot prevent services and characteristics being discovered. You can advertise a primary service and have secondary services that aren't advertised, but once a connection is made all services and characteristics will be revealed.
If you specify that an attribute requires encryption, then a pairing (technically bonding) process will be initiated when you first try to read/write the characteristic. This process exchanges encryption keys and results in the devices 'trusting' each other.
If your peripheral and central are both iOS8 devices, then I have found that if both devices are configured with the same iCloud account then the trust is already established (presumably for functions such handoff) and you will never see the pairing dialog. This caused me quite a bit of confusion when I was trying to test encrypted characteristics.
If you test using devices with different iCloud accounts then you should see the pairing dialog.
Even the pairing process will not "protect" your service/characteristic if the "attacker" has control of both devices as they can simply complete the pairing process. Pairing/bonding does protect the data against eavesdropping as the transfer will be encrypted.
To actually protect the service you would need some form of challenge/response involving a characteristic before exposing data.
For example the central needs to read a value from characteristic "A" which is set at random by the peripheral. The central then needs to calculate the correct response to that value and write it back to "A". Only if this value is correct does the peripheral set values on the remaining characteristics (or accept inputs on the other characteristics from the central).
This solution is only secure as long as your challenge/response mechanism isn't compromised but will probably defeat non-determined attackers.
The basic problem I'm trying to solve is as follows. I have two iOS devices, one configured as a central, and the other as a peripheral. I would like the peripheral to know if the central moves away or becomes inactive for some reason (say the device running the central is turned off).
Under normal conditions, I have it set up so that as the central moves close to the peripheral, the central can use beacon regions and ranging to inform the peripheral via a characteristic-write when it is in the immediate proximity (CLProximityImmediate), and then again when it is still in range, but far off (CLProximityFar). This works well.
However, to catch a corner condition when the central device goes from being CLProximityImmediate to some unknown state, I was planning to used periodic indications from the peripheral to which the central can respond. If there is no response to the indication, then the peripheral can assume that the central is no longer in immediate proximity. However, I am not able to find a callback or delegate method that informs the peripheral manager that the indication failed.
The method updateValue:forCharacteristic:onSubscribedCentrals: returns NO when the underlying transmit queue is full-- and NOT because the central did not respond, the way I've understood it.
Am I missing something obvious here? Is there a way for the peripheral manager to tell that the central did not receive an indication? Or is there a missing callback in CoreBluetooth for this case?
Thanks for your help!
There are two issues to let the peripheral know the location maneager lost contact with the CLBeacon:
The iBeacons are passive, advertise and transmit only devices. As in the Apple implementation there are no connection made to them from the Core Location framework. Therefore the peripheral newer knows anyone listened to their broadcast.
The CLBeacon will not expose the underlying BTLE peripheral as a CBPeripheral, so you would not be able to let the BTLE peripheral know your location monitor was in range.
Issue #2 can be resolved a rather complicated way:
Start scanning for CBPeripherals where the advertisementData contains the proximityUUID.
Make connections to that CBPeripheral and get notified of a characteristic value change.
Change a characteristic value on the peripheral frequently, so when the Central sees no more notifications after a timeout period it indicates the peripheral is out of range.
If you want to make it work for multiple iOS devices this gets way to complicated to switch between advertising and iBeacon and working as a GATT peripheral.
Although this was not explicitly stated in the question, implicitly, I was stating that the peripheral was advertising a service with a couple of characteristics. This is in addition to the explicit iBeacon advertisement, which I agree is "passive". When the central is notified of the beacon region, it discovers and writes to one of the characteristics, and the peripheral can know that a central is in range.
My fundamental question is not regarding the passive nature of the beacon, but more about how "indications" work with CoreBluetooth. I think it is fairly safe to say that if a peripheral uses updateValue:forCharacteristic:onSubscribedCentrals: to inform a central subscribed to a characteristic defined as CBCharacteristicPropertyIndicate, there is no callback defined to capture the confirmation to the indication that should come back from the central (per GAP).
One workaround to this missing API is to define a third characteristic as a "confirmation" characteristic, and impose a protocol that the central should do a dummy read on this characteristic whenever the central receives peripheral:didUpdateValueForCharacteristic: to the indication which was subscribed. That should produce a call to peripheralManager:didReceiveReadRequest: on the peripheral to confirm that the indication was received.
This is more work, and not the most elegant way to do it, but it may be the best thing to do given that there is no API for indication confirmations.