Arduino Tutorial: Integrating Bluetooth LE and iOS with Swift

Hi samra,

BTService and BTDiscovery are separate classes because the represent objects with different purposes. For example:

  • BTDiscovery lives throughout the lifetime of the app. It is a CBCentralManagerDelegate which catches delegate callbacks like when a new peripheral has been discovered or connect or disconnected, etc.

-BTService is an instance (created by BTDiscovery) of a connected peripheral and only lives upon a live connection to specific peripheral and dies when disconnected. Technically you could have several instances of BTService. It is a CBPeripheralDelegate which letā€™s BTService discover the Services and Characteristics of a connected peripheral. It also can receive callbacks when values change in GATT Characteristics and can write new values.

So this leads to your question of how to get a list of Bluetooth devices. Since BTDiscovery knows when new devices are discovered, have it send out a notification and let your tableview delegate update its local array and then call tableview.reloadData(). The cellForRowAt will grab up the new device and display to the user.

Comments to consider:

  1. Inside your cellForRowAt and didSelectRowAt, you donā€™t need to cast indexPath to NSIndexPath. Its class type is IndexPath which already has the row property.
    i.e.:
    label?.text = peripheral[indexPath.row].peripheral.name

  2. Inside your cellForRowAt, I donā€™t recommend referencing a UILabel based on its tag in this situation. Either use custom cells and attach IBOutlet connections to storyboard,
    OR
    Use one of the non-custom cell options in the storyboard like Right Detail. Then you can reference the labels like this:
    cell.textLabel?.text = ā€œsome nameā€
    cell.detailTextLabel?.text = ā€œsome other infoā€

  3. You need to consider how you are going to handle when BLE devices come in and out of range, or are turned on and shut back off. Also your tableview needs to update itself to give current status of connections (connected/disconnected).

  4. Iā€™m not trying to push my book on you, but it covers how to handle everything you are wanting to do plus more. It builds a complete working Bluetooth app from start to finish. The app is on the app store called Pad To PIC, if you want to take a peak at the functionality taught in the book. The book, Integrating iOS Bļ»æluetooth LE with PIC18 Miļ»æcrocontroļ»ællers, is available from my website at www.back-40.com. It is based on Objective C, BUTā€¦ I send the book source code in the latest Swift 3 version upon request from buyers.

  5. And lastly, as you are finding out, BLE development can have a steep learning curve at times. If you feel that you need professional help in this area, Iā€™m a full time freelancer who develops projects like this on a daily basis. Feel free to contact me :slight_smile:

~ Owen

Hello Owen,
great tutorial.
I made your app run with a iPhone 6, HM-10 Modul, Arduino and s servo.
The next step was to sent measured values as a string from Arduino to iPhone.
(My idea is to use my iPhone as

  • a bluetooth radiocontrol for modell car/ship with short range telemetry or
  • simple display measured values from Arduino)
    So I added ā€œdidUpdateValueForcharacteristicā€ to your BTService.swift. Yes it works, but
    please see the comment in my image.

In my next post a want show you the code in a further image.

Best regards Jƶrg, from Germany.

The next step is update the label with "didUpdateValeForcharacteristic - I made a function "func_updatelabel " please see the image

But if I decomment ā€œself.labelvalue.text=ā€¦ā€ the app compiled well but crashed with fatal error if I send data.

Is it possible to give me help?

Best regards Jƶrg

Hello Owen
you see Iā€™am testing.
Do you have any idea for me.

Jƶrg

Hi joerg,

The CoreBluetooth callback ā€œdidUpdateValueForā€ is not running on the main queue.
Try:

DispatchQueue.main.async {
self.labelvalue.text = ā€œIā€™m on the main queue with the other gui objects :slight_smile: .ā€
}

-Owen

Hi Owen

I am trying similar to receive data from Arduino to iPhone via BLE, below are code snippets based originally on the helpful tutorial. I am struggling with (among other things) where code should be located to point/connect a class to another class / ViewController. Any help would be GREATLY appreciated. I am new to Swift.

Xcode 8.2.1, Arduino Uno R3, Black Widow shield V1.1

ARDUINO SEND DATA

void loop() // Continuous loop
{
// See if new data is available
if(BLE_Shield.available()) {
BLE_Shield.write(ā€œTestā€); // send message
}
}

ā€¦

LABEL

@IBOutlet weak var labelvalue: UILabel!

ā€¦

IDENTIFY RX

let frequencyCharUUID = CBUUID(string: ā€œA9CD2F86-8661-4EB1-B132-367A3434BC90ā€)

ā€¦

BLUETOOTH SERVICES

class BTService: NSObject, CBPeripheralDelegate {

var peripheral: CBPeripheral?
var frequencyCharacteristic: CBCharacteristic?

ā€¦

 func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic) {
        
        let RXvalue: String = String(data: characteristic.value!, encoding: String.Encoding.utf8)!
        var RXstring = RXvalue
     
        DispatchQueue.main.async {
            self.labelvalue.text = ("\(RXstring)")
        }
    }

COMPILE ERROR! Value of type ā€˜BTServiceā€™ has no member ā€˜labelvalueā€™

I also tried sending RXstring to a function outside the BTService class to update the label but get the error message ā€˜Instance member ā€˜label valueā€™ cannot be used on type ā€˜ViewControllerā€™ā€™

A_M

Hi a_m,

A couple comments:

  • The Arduino code you posted will only transmit if it first receives something. Is this what you want? Maybe so for testingā€¦
  • BTService isnā€™t a UIViewController class. Therefore it doesnā€™t have a UILabel object (and shouldnā€™t). So using ā€˜self.labelvalue.text = ā€¦ā€™ isnā€™t going to work as you have seen.
    Soā€¦ you want your UI to show the data received in the didUpdateValueFor function. You can do that a few different ways, but for this case Iā€™d recommend using NSNotifications. The didUpdateValueFor function would post the notification and your UIViewController (which has the UILabel) would register to listen for the notification. And be sure to move the DispatchQueue.main.async code to the notification selector function to insure the UILabel text field is written to on the main thread.

Owen

Hi @owenb,

Thank you for the great tutorial. Helps A LOT!

But, how can we connect and get/send data for multiple peripherals that are the same?

For example, Iā€™ve made my own heart rate monitor(s) with the Arduino and created a Swift class for the Bluetooth that sends the data as a delegate so I can simplify adding code to the ViewController. Works great (and works with the Polar Bluetooth HRM).

Basically I add the delegate and create an instanceā€¦

class ViewController: UIViewController, BluetoothHRMDelegate {
         
         var hrm = BluetoothHRM( )

Add the delegate function(s)ā€¦

func bluetoothHRMHeartRate(_ value: String) {
    lblHeartRate.text = value
}

And set the delegate in the viewDidLoadā€¦

    super.viewDidLoad()
    hrm.delegate = self
    lblHeartRate.text = ""

ā€¦

Here are the BluetoothHRM.swift and ViewController.swift files.

ā€¦ But ā€¦

Iā€™d like to monitor the heart rate data from 3 peripherals to iOS. (3 Arduinos attached to 3 difference people) and not sure how to do this. Any suggestions on how to implement this as all the peripherals are identical?

  • Can I create an array of hrmPeripheral?
  • Can the centralManager keep track of multiple peripherals?
  • Can I create a hrm array in the ViewController (or hrm1, hrm2, hrm3) and somehow distinguish between them in the delegate call back - by adding some type of ID in the bluetoothHRMHeartRate call back or adding making my own ID and sending that with the init call of the BluetoothHRM (but what if central drops and connection and reconnects)?

Appreciate any help or suggestions.

Thank you,

Blaine

Hi bleckett,
Even though all 3 devices support the same BLE Services & Characteristics, they will still have a unique peripheral identifier when discovered by Core Bluetooth. The identifier property of CBPeripheral will be the same for the same device regardless of the number of times connected/disconnected or timespan between connections.

  • Yes, you can create an array of hrmPeripheral. I recommend having the array as a property of the shared BTDiscovery instance since it needs to initialize an instance of BTService (CBPeripheralDelegate) for each device connected toā€¦ and remove the device from the array when disconnected.
  • Yes, the centralManager supports multiple connections. But you need to keep track of which one transmitted new data by examining the identifier property.
  • Like I mentioned above, just use the built-in peripheral.identifier property. I donā€™t recommend creating your own.

Owen

Hiā€¦ im using this code but I have a little problemā€¦ I need to terminate the connection to mi BLE Peripheral and I canā€™t do it, I leave the app and the BLE keeps connected, I use a segue to go to another scene that automatically connect to star using your code.

Hi riatzur,
BLE connections remain until:

  1. Your app is shut down (not just in background).
  2. Your code calls centralManager?.cancelPeripheralConnection(peripheral)
  3. User manually turns off Bluetooth.

If you want to allow user to disconnect, then add a connect/disconnect button that calls centralManager?.connect(peripheral, options: nil) OR centralManager?.cancelPeripheralConnection(peripheral) based on the current connection status.

Hi Owen,

thanks a lot for this great tutorial! I am going to use this as a starting point for a bigger project. :wink:

I have a question about the Black Widow Shield: Will it block any pins when mounted on a Mega board? I have to use this board for my project.

Best regards and thanks a lot,

Marcel

Hi marcelKraus,

In order to communicate with the Arduino it uses a minimum of 2 pins for TX/RX. There are 2 additional pins that can be consumed (optionally) if the OPT_RESET & OPT_CONNECT lines are used. I recommend downloading the Black Widow datasheet for exact pin locations from my Downloads page. Once ordered, the Black Widow comes with a full schematic of the PCB.

Thanks,
Owen

Awesome!!! Youā€™re the best Owen! Thanks a lot!

I need to create a Class reference to the BTDiscovery? Or I can do it directly from my Swift class? Im relatively new to this languageā€¦

Or maybe can I create a function in BTService with centralManager?.cancelPeripheralConnection(peripheral)
on it and the call it from my scene UIButton?

Hi Owen,

thanks a lot!

This should be no problem to reserve these 2-4 pins for the shield; my only concern is that the shield will physically block some of the Mega ports on the side of the board.

I refer to this pictures: https://koenig-media.raywenderlich.com/uploads/2014/10/IMG_4959-reduced-428x320.jpg and http://digipak.org/wp-content/uploads/2012/10/p-364-arduino_mega_adk_1_600px.png

Best regards,

Marcel

Great tutorial!! Thanks a lot.

I need to do this with my Raspberry Pi Zero W. Can you help me please to understand, what and where I have to change in order to use your tutorial in my project with Raspberry Pi Zero W?

The goal is the same, I need to connect my iOS (Swift 3) application to my Raspberry Pi Zero W via BLE protocol and interacting with.

thank you in advance

Hey riatzur,

Since BTDiscovery needs to be alive the lifetime of the app, its setup as a singleton.

To cancel connection Iā€™d do something similar to this inside BTDiscovery:

public func disconnectPeripheral(peripheral: CBPeripheral?) {
if let peripheral = peripheral {
// Put device on ignore array so autoconnect doesnā€™t kick in again.
ignoreAutoConnectDevices.append(peripheral)
centralManager?.cancelPeripheralConnection(peripheral)
}
}

Owen

Hi marcelkraus,

Yeah from the images it looks like some ports will be covered.
You will either need to extend the shield up or make connections from the bottom of the Mega (i.e. desolder/resolder headers or similar).

If you plan to connect to the other back ports with another shield type, then maybe just plug the Black Widow on top of the other shield.

Hope this helps :]
Owen

Hi sydibyd,

Sorry but I donā€™t have a Raspberry Pi on hand, but here are some things to keep in mind:

  • Make sure the GATT setup in Core Bluetooth matches the BLE moduleā€™s GATT. (Services/Characteristics UUIDs)
  • Ensure you handle data flow correctly on the microcontroller side to prevent data loss. (no long running loops that cause UART buffer overflow.)

For a basic BLE app, the tutorial shows the main components to be configured/programmed:

  • Core Bluetooth setup
  • microcontroller firmware to handle incoming/outgoing data to BLE transport
  • GUI input/output of BLE data

I know this is general info but hopefully it gives some guidelines to help.
Owen

1 Like

Hey Owen, or anyone else who may be able to help.

Iā€™m currently trying to only receive data being sent over BLE.Print from my Arduino. So, I should be looking for the RX characteristic.

I can see the data being transmitted in the UART window on the Bluefruit LE App. I already can connect to the device in my own application. I got the UUIDs I need from the Nordic Semiconductor App.

I donā€™t know how to see the UART RX data on the iPhone app.

Is there anywhere you could point me?

For reference:

  • Adafruit Bluefruit Feather M0**
  • UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E (Nordic UART Service)
  • UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E (RX)
  • UUID: 0x2901 (Characterisic User Description)
  • UUID 0x2902 (Client Characteristic Configuration)
  • UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E (TX)
  • UUID: 0x2901 (Characterisic User Description)