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.
Related
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
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!
When doing Bluetooth communications one is often placed in a situation where one does a call which gets a response in a delegate, for example the characteristic discovery shown below:
func discoverCharacteristics(device: CBPeripheral)
{
servicesCount = device.services!.count
for service in device.services!
{
print("Discovering characteristics for service \(service.uuid)")
device.discoverCharacteristics([], for: service)
}
}
Now this discovery is not for a specific device but for health devices following the Bluetooth SIG services/profiles, so I don't know exactly what services they might have and I don't know how many characteristics might be in each service. The method is asynchronous and the answers are signaled in the following delegate method:
// Discovered Characteristics event
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
for characteristic in service.characteristics!
{
print("Found characteristic \(characteristic.uuid)")
}
servicesCount = servicesCount - 1;
print("Characteristics sets left: \(servicesCount)")
if servicesCount == 0
{
print ("Found all characteristics")
DispatchQueue.main.async{
self.btleManager!.btleManagerDelegate.statusEvent(device: peripheral, statusEvent: Btle.CHARACTERISTICS_DISCOVERED)
}
self.device = peripheral
self.handleMds()
}
}
Now I need to wait until the discovery is done before I can take the next step because what I do next often depends on what I got. In Java and on Android what I do is wait on a CountDownLatch in the calling method and I signal the latch in the callback to release that wait.
The iOS equivalent of the CountDownLatch seems to be the DispatchSemaphore. However, doing so apparently blocks the system and no delegate ever gets called. SO what I have done (as shown in the code above) is to initialize a variable servicesCount with the number of services and in the delegate callback decrement it each time it is signaled. When it gets to zero I am done and then I do the next step.
This approach works but it seems hacky; it can't be correct. And its starting to get really messy when I need to do several reads of the DIS characteristics, the features, the various time services, etc. So what I would like to know is what is the proper method to wait for a delegate to get signaled before moving forward? Recall I do not know what services or characteristics these devices might have.
First of all, if you already have an implementation with CountDownLatch for Android, you may just do the same implementation for iOS. Yes, Swift doesn't have a built-in CountDownLatch, but nice folks from Uber created a good implementation.
Another option is to rely on a variable, like you do, but make it atomic. There are various implementations available online, including one in the same Uber library. Another example is in RxSwift library. There are many other variations. Atomic variable will provide a thread-safety to variable's read/write operations.
But probably the most swifty way is to have a DispatchGroup. Will look something like this:
let dispatchGroup = DispatchGroup() // instance-level definition
// ...
func discoverCharacteristics
{
for service in device.services!
{
dispatchGroup.enter()
// ...
}
dispatchGroup.notify(queue: .main) {
// All done
print ("Found all characteristics")
DispatchQueue.main.async{
self.btleManager!.btleManagerDelegate.statusEvent(device: peripheral, statusEvent: Btle.CHARACTERISTICS_DISCOVERED)
}
self.device = peripheral
self.handleMds()
}
// ...
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
// ...
dispatchGroup.leave()
}
In other words, you enter the group when you are about to submit a request, you leave it when request is processed. When all items left the group, notify will execute with the block you provided.
You only get a single call to didDiscoverCharacteristicsFor for each call to device.discoverCharacteristics. That is, the didDiscoverCharacteristicsFor call is only made once all of the service's characteristics have been discovered.
You have the peripheral passed to the delegate call, so you have the information you need to know the context of the delegate call. There is no need to "wait". You can use a simple state machine per peripheral or service if you are discovering data for multiple peripherals/services simultaneously.
You can even keep a set of peripherals that aren't in a completely discovered state if you need to take some action once discovery is complete; e.g. You can remove a peripheral from the set once discovery is complete for that peripheral. If the set is empty, discovery is complete for all devices.
All of this state belongs in your model. All your delegate implementation should need to do is update the model with the data that has been discovered.
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.
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