didWriteValueForCharacteristic being called after writing withoutResponse - ios

We are seeing a case where writing data without response:
peripheral.writeValue(data, forCharacteristic: characteristic, type: .WithoutResponse)
Is still getting a callback to:
func peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
The error is nil and the char is the one we just wrote to (.WithoutResponse). Writing using .WithResponse also gets the callback as one would expect.
What could cause such behavior?
iOS 9.3, iPhone 6s+, Xcode 7.3.1, iOS Central, Ti CC2640 BLE Stack 2.1.0 Peripheral
Update 1:
The properties on the characteristic are: .WriteWithoutResponse, .Write, .Notify (verified via characteristic.properties)

It was exactly the case for me on iOS10.0 and iOS10.1 Things went back to normal on iOS10.2 beta3 released Nov'14th 2016.
More details about CoreBluetooth state machine bugs under iOS10.0 10.1 can be found here: https://forums.developer.apple.com/thread/51960

Related

(iOS / CoreBluetooth) Identify corresponding "writeValue" call in "didUpdateValueFor"

Context
My peripheral has a characteristic which is used for sending commands but also for reading different values by sending write commands.
You can interpret these commands as I would say...
... "Hey peripheral, next time I read your characteristic xyz I am interested in the following attribute..."
Example workflow on that characteristic is:
Send read command by using peripheral.writeValue(data, for: characteristic, type: .withResponse)
Retrieve response of read command by calling peripheral.readValue(for: characteristic)
Reading value of the characteristic in callback peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?). This is my response I have to validate. Here I need to identify what command I did send in 1). But since the characteristic is always the same and also the characteristics value does not give a hint on the previous write I have no clue how to deal with this.
Question
How can I match the events I receive in didUpdateValueFor to the initial "write" call?
What I thought about as solution:
Create a simple FIFO queue but I guess I can't rely on the call order and this also does not handle possible error cases (write did fail)
Making the whole process synchronous (maybe by using actor with Swift 5.5?) but I guess this would give me other drawbacks concerning notify values from other characteristics...
Cheers 🍻
Orlando

Unable to recieve gyroscope data via Bluetooth LE, from an Arduino onto my iPhone App (iOS, Swift)

I currently am making a test iOS app that communicates with my Arduino Uno board over bluetooth.
I am using an HM-10 BLE module, and an MPU-6050 gyroscope & accelerometer.
I started the project by attempting to toggle an LED on and off, which is working fine.
Now, I would like to have the current xyz values of the gyroscope displayed on my iPhone.
It seems much of the work needs to be done in the inside the didUpdateValueFor function. Then use the data from there to update a text field.
I am testing by writing out only the x value of the gyroscope right now. It displays properly on the arduino serial monitor, and it would also display when I connect via bluetooth to my PC.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == peripheralManager.hm10CharacteristicUUID {
debugPrint("Writing Data")
let myData = characteristic.value
let rollText = String(data: myData!, encoding: String.Encoding.utf8) ?? "roll not found"
debugPrint(rollText)
}
}
The output of this code just prints "roll not found", over and over, like the gyro data prints out to the serial monitor
Would greatly appreciate any help with this!

centralManager(_:didDisconnectPeripheral:error:) not called

In a CoreBluetooth related iOS app written in Swift (4.2), I have the following problem.
The app handles a certain number of Peripherals and Centrals.
Each Peripheral provides a service and has several Centrals connected to it; it keeps an accounting of that and knows how many Centrals are connected.
In the same way each Central is connected to several Peripherals; and also keeps an accounting of that to know how many Peripherals are connected.
The question I have is about the book-keeping to maintain the accounting I was just mentioning, up to date.
From the Peripheral side, it works and I can see when a Central has disconnected.
For that I use:
peripheralManager(_:central:didSubscribeTo:)
and:
peripheralManager(_:central:didUnsubscribeFrom:)
From the Central I want to use:
centralManager(_:didConnect:)
and:
centralManager(_:didDisconnectPeripheral:error:)
But here, for some reason, it does not work. The first function (centralManager(_:didConnect:)) is called as expected but not the second (centralManager(_:didDisconnectPeripheral:error:)).
I suppose this last issue of the function not being called is what I need to solve. But I may be wrong.
As a result, I see when a Peripheral is coming but not when it is leaving.
What am I missing? Or am I just doing it wrong?
I test the app using two devices.
--- Further information --- (Post update)
Here is some of the related code:
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber) {
print(#function)
if cbPerifList == nil {cbPerifList = [CBPeripheral]()}
if let perifIndx = cbPerifList!.index(where:{$0.identifier==peripheral.identifier}) {
central.connect(cbPerifList[perifIndx], options: nil)
} else {
peripheral.delegate = self
cbPerifList.append(peripheral)
central.connect(peripheral, options: nil)
}
}
One more possibly relevant thing I noticed is the following. Instead of switching of the peripheral by a button switch as I should normally do, I force kill the app on the device currently playing the peripheral role, then the centralManager(_:didDisconnectPeripheral:error:) function is called on the other device as I would expect it to be; and the accounting is correctly performed.
In my CoreBluetooth App
func centralManager (_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
}
Is called when ever peripheral is lost.
Maybe check your syntax of your didDisconnectPeripheral func.
I did not find a direct answer to this question.
Reading the comments seems to show there is now answer. In other words what I wanted to do sounds impossible. If some expert happens to know otherwise, please let us know.
In the meanwhile I made a work-around solution:
When shutting of one peripheral, I use one of its characteristics to set some non-sense value which is then used as hint by the central to know that the peripheral has "said good-bye"..... And it works.

A weird message in CoreBluetooth / CBCentralManagerDelegate

In my implementation of the CBCentralManagerDelegate protocol, I have the following function.
func centralManager(_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: Error?) {
print(#function)
if error != nil {
print("Error in \(#function) :\n\(error!)")
return
}
......
// More useful code irrelevant to the question.
}
When the above function is called I can see the message below in the Xcode debugging console.
centralManager(_:didDisconnectPeripheral:error:)
Error in centralManager(_:didDisconnectPeripheral:error:) :
Error Domain=CBErrorDomain Code=7 "The specified device has disconnected from us."
UserInfo={NSLocalizedDescription=The specified device has disconnected from us.}
Here is my question:
I must be missing something (because too simple or too subtle), but why does is show an error because "The specified device has disconnected from us."
In the centralManager:didDisconnectPeripheral function, what else could I expect other than the device being disconnected?
I hope some enlighted expert can bring some light an explain why this is so.
As per the Apple documentation:
If the disconnection was not initiated by cancelPeripheralConnection(_:), the cause is detailed in error.
I.e if You disconnect then you get no error but if They disconnect you see that through the error.
Source: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518791-centralmanager

Sending command to Bluetooth LE Device from a iOS App

I'm building a simple IOS app for interacting with a Bluetooth LE device.
Main steps ( Connecting, discovering services, discovering characteristics and reading characteristics' values ) are done rightly.
Now i'm searching for a tutorial/example for sending command to the Bluetooth LE device i'm connected on.
I'm searching in the Apple Developer Guide but i'm not finding anything.
You should check out Apple's TemperatureSensor example, specifically where it calls [servicePeripheral writeValue:data ...
When interacting with a CBCharacteristic I am going to assume it is a read/write characteristic (you can confirm this by looking at the value for properties on your characteristic).
The two main methods when interacting with a characteristic are:
func writeValue(_ data: NSData!, forCharacteristic characteristic: CBCharacteristic!, type type: CBCharacteristicWriteType)
func readValueForCharacteristic(_ characteristic: CBCharacteristic!)
Both of these methods are found on your CBPeripheral. Once you have called one of these functions you can utilize the CBPeripheralDelegate to confirm each of these actions in these delegate methods:
optional func peripheral(_ peripheral: CBPeripheral!, didWriteValueForCharacteristic characteristic: CBCharacteristic!, error error: NSError!)
optional func peripheral(_ peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error error: NSError!)
These are the places you will look to confirm your read and write's success. (inside of your read you can retrieve the value that was read from the BLE Device off the value property on CBCharacteristic.
Keep in mind the interactions you have (what you can read and write) are entirely dependent on the BLE device you are interrogating/interacting with. Essentially you must know what to read, what to write, and how.
You can send a data with writeValue:forCharacteristic:type: method of CBPeripheral. What characteristic+data match the required commands must be described in device specification.

Resources