I'm currently working with a Corebluetooth, with my phone acting as central, and a separate peripheral.
I'm successfully reading data from a peripheral device using the didUpdateValueFor delegate method. The problem I'm having is when I'm sending multiple packets of information at the same time.
For instance, I send "abc" first and "def" later. As the central updates the reading upon indication from the peripheral, I should be able to get "abcdef" at the end. This works fine if I am sending indications at a speed of 10 packets per second.
However, once my speed gets to the default indication speed, it's too fast for the central to keep up. I only get the first indication "abc", but I never receive the indication for "def".
Is there a way I can force the didUpdateValueFor method to run concurrently so it captures all incoming notifications regardless of speed?
The best-practices chapter of the Core Bluetooth Programming Guide recommends the use of subscription via setNotifyValue:forCharacteristic: rather than plain reads for characteristics that will change often.
It isn't clear from your question as to whether you are using subscription or polling via readValueForCharacteristic:
I think that you mean didUpdateValueForCharacteristic instead of didWriteValueForCharacteristic. Otherwise, the question doesn't seem correct as didWriteValueForCharacteristic can not be used to read data from the remote device (except if you are using error codes for communication - which you shouldn't ;) ).
There are two methods to push data from the peripheral to the central: indications and notifications.
Notifications may be discarded if sent too fast or for whatever reasons.
Indications can only be sent one at a time (you'll have to wait until the central replies with a confirmation that the indication has been processed, before sending another one!).
I see multiple ways how your current implementation may be incorrect:
You are not waiting for the Handle Value Confirmation packet and send the next indication too early.
You are sending indications / notifications before the Client Characteristic Configuration has been written by the central.
Your peripheral has flagged the characteristic to support both notifications and indications. In this case, Core Bluetooth only supports notifications and doesn't enable indications, in which case you are back at unreliable notifications ([CBPeripheral setNotifyValue:forCharacteristic:])
If the specified characteristic is configured to allow both notifications and indications, calling this method enables notifications only.
Related
On iOS some data packets are lost via BLE.
On a characteristic we have the type .withoutResponse we check of course for canSendWriteWithoutResponse and func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral).
This is also stated in the documentation.
On the other hand, if you specify the write type as
CBCharacteristicWriteType.withoutResponse, Core Bluetooth attempts to
write the value but doesn't guarantee success. If the write doesn't
succeed in this case, you aren't notified and you don't receive an
error indicating the cause of the failure.
Anyone have an idea how I can fix this without switching to withResponse ?
BLE is a distributed protocol. It is always possible to lose data. The only way you can ensure that you haven't lost data is to receive some notification back that the data was received correctly (often called an ACK for Acknowledge). The standard BLE way to achieve this is with responses. But you're free to implement your own ACK scheme if you don't want to use the standard one.
As an example, you can create a "confirmation" characteristic in your peripheral that your central subscribes to. When you send a write, you would wait until you receive a notification from that characteristic. If you don't see a notification after some timeout, the write may have been lost. This re-implements the standard write-with-response system at a higher layer.
Several of the BLE protocols I work on are actually serial protocols (we write to a characteristic, and read responses from the same characteristic). We often implement the ACK directly into the serial protocol (for example, returning <msgid>OK) without needing a BLE write-response.
Another solution is to write to the characteristic, and then read from the characteristic to see if it's updated, provided the characteristic is symmetric in that way (many aren't). I am fairly certain that there's no write-caching that would bite you here, since BLE reads and writes often aren't symmetric, but you should double-check that before relying on it. (See the Core Bluetooth WWDC videos from 2019 and 2017.)
As with all distributed systems, it's also never possible to know that data was lost. It's possible that the data arrived, and that the response was lost. This is the nature of distributed protocols, and just has to be figured into the system. This is why it's possible to build a system that will deliver a message at least once (provided that delivery is ever possible), or a system that will deliver a message at most once, but it's impossible to build a system that will deliver a message exactly once.
If you have no control over your peripheral, and it doesn't provide any error detection mechanisms, then there's nothing you can do on the central (phone) side. You have to have some cooperation from both sides to build a fault-tolerant distributed protocol.
We have a battery characteristic on our peripheral device that is both readable and notifying.
In Android, it's easy, there's a callback function for read responses (onCharacteristicRead) and one for notifying packets (onCharacteristicChanged).
But in iOS, there's only one callback function for both reads and notifications (didUpdateValueForCharacteristic) and we can't seem to find a way to identify which peripheral action is happening (read or notify).
Is there a way to know if we're getting a read response or a notification?
Note that for characteritics that are only readable or notifying, we don't have any problem and the code works like a charm.
There is no way to identify the read response and the notification because they comes from the same callback without identifier.
If you want to differentiate them for special case, you may need to use two protocol (one for read, another for notify) in the firmware side. So you can differentiate them in the App with your protocol. Generally, we don't differentiate them.
I’m working on a simple wrapper around CoreBluetooth to send any data to any device.
During developing I encountered a lot of bugs in framework, they were very annoying and to make my wrapper stable I had to shorten some of functionality for reliability.
For now I’m working on sending data from peripheral.
Ok, so I have following case:
Client asks for value of dynamic characteristic
I get a callback on server-side - peripheral:didReceiveReadRequest:.
Note : I need to respond to this CBATTRequest in this method - I can’t store it elsewhere and respond to it asynchronously. (Im just putting some chunk #“PrepareToReceiveValue” that will be ignored on central side. All sending is done in queue.)
For providing data for various devices I constructed a queue with BTMessage's in it. (So for readRequest I create message and add it to sending queue. If chunk sending failed - I will get a callback from peripheral manager about readyToUpdateSubscribers and will ask queue to resend failed chunk)
So when I’m requesting immediately a lot of dynamic characteristic values and sending data from peripheral to central concurrently sometimes it just freezes sending progress and leads to disconnection.
After several testing I found out that it was all about transmit queue:
If transmit queue is full and you will receive read request - it just won’t respond to it.
So I have potential unstable system state:
Peripheral is sending data to some central.
In my sending method updateValue:forCharac… returns NO because transmit queue is full.
At this moment central requests dynamic value for characteristic and peripheral:didReceiveReadRequest: invocation will be added to current runloop.
After returning from sending method it will dequeue peripheral:didReceiveReadRequest: method and responding to this request will have no effect (transmit queue is full).
So in this case respondToRequest: is ignored like I didn’t invoked it at all.
CoreBluetooth will not be able to send/receive any data until I will respond to request. That was the reason for freezing any sending/receiving progress with concomitant disconnection.
As I mentioned before - I must respond to request in appropriate method - otherwise it will also have no effect. (Im saying it because I’ve tried to put those request in array if queue is full and respond to them when it will have some space but with no luck).
Im waiting for your proposals/suggestions how to resolve this problem, any help would be appreciated.
I have been using CoreBlueTooth framework to communicate between BTLE iOS devices and I see a strange behavior. Here is what I am doing:
iOS device 1 (Peripheral): Expose a writable characteristics.
iOS device 2 (Central): Scan for the writable characteristics and write data into it.
iOS device 1 (Peripheral): Receives write request. Wait for some time to acknowledge the receipt of data.
iOS device 2 (Central): Get a callback on the below delegate and received the mentioned error.
Issue: Here if I respond back to the write request in few seconds by calling the API [iPeripheral respondToRequest:iRequest withResult:iStatus] then it all works fine and I get a success on my Central. But if I take some time, even if my Peripheral has not responded to the write request, I get error response back.
Is this some kind of connection loss in few seconds or the known CB framework behavior, any idea?
- (void)peripheral:(CBPeripheral *)iPeripheral didWriteValueForCharacteristic:(CBCharacteristic *)iCharacteristic error:(NSError *)iError
Error Domain=CBErrorDomain Code=0 "Unknown error." UserInfo=0x183a6d70 {NSLocalizedDescription=Unknown error.}
Both my Central and Peripheral are running on iOS 7.0.
I also observed this problem when I had deadlocks in my code and couldn't respond in time ;-) The way I observed it, iOS responds with an automatic error request with an arbitrary error code if a request is not answered within 10 seconds. I have not found a way to change this, but it makes sense from a protocol perspective.
In Bluetooth Low Energy, a central can only send a single Characteristic Value Write Request at a time. After it has sent this request, it cannot send a different Write Request unless the first one is responded to. Therefore, it is crucial to always respond to requests as fast as possible.
In the comments, you mentioned that you are waiting for user input to affect the result code you want to send to the central. I guess "Success" if the user confirms in the UI that an operation should be started, and an error code if the user denies that. This is not the way an LE based protocol should be designed. It's like blocking the UI thread until an operation is finished, just from the other side. You are effectively blocking the BT communications until a blocking operation (waiting for user input) completes.
A different design would be to send a write request to the other phone, responding immediately with a "Success" error code to indicate that the request was received and the popup is displayed. Then, send a Characteristic Value Indication with the user's choice from the peripheral to the central.
There's one small caveat if you target iOS 6: indications don't work nicely in many cases (reentrancy bugs etc, best not touch them). There, you should send a Read Request from your central and return the user's choice in this read request if it's already available. Again, don't block while giving the answer, sending back a "user is still choosing" value back if the answer is not yet ready.
Single rule: Answer requests as fast as possible. It's the way, Bluetooth LE is designed to work.
You may be exceeding the maximum time allowed for a write to be acknowledged. Try testing several different ack times and see if it reliably fails beyond a certain threshold.
If you use iPhone 4 devices, this device no suports BLE. BLS are supported in iPhone 4 and later.
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.