Arduino Tutorial: Integrating Bluetooth LE and iOS with Swift

Hi John,

Using BLE discoveries to detect iOS devices in the area may not work so well. This is because most iOS devices are not advertising as BLE Servers (slaves). They are typically used as the Client (master) and therefore are externally silent. And actually they aren’t even performing master operations unless a specific app is telling them to. An iOS device can advertise as a slave but again it would need to be a 3rd party app doing it as I don’t know of any built-in Apple applications supporting it.

The link you referenced indicated a BT Classic module. But again this won’t necessarily detect iOS devices unless a specific app is broadcasting. The website mentioned about grabbing MAC addresses while next to a busy highway. The addresses grabbed may have been from Bluetooth peripherals like car’s built-in BT or headsets. Its hard to say.

Also, just a note that the MAC addresses discovered were technically Bluetooth addresses. For most purposes, a BT address can still be considered a unique identifier but wanted to mention it.

Apple devices do support both Bluetooth Classic and BLE but from my experience you won’t be able to detect them by default. You might be better off checking Wifi signals as many phones have their personal hotspot broadcasting.

You can do some quick manual testing for BLE by downloading an app like BLExplr or similar. It scans all the BLE devices in the area if they are advertising. For BT Classic, just open the Bluetooth settings view and it will search for BT Classic devices.

-Owen

Hi Owen

Thanks for the explanation, that has answers many questions.

Just to clarify though! Am I right in thinking that even if my iOS devise is actively scanning for available Bluetooth devices it isn’t in fact broadcasting anything that can be detected by a Bluetooth module rigged up to my arduino. And this is because it is a master not a slave.

Thanks again for your help

Kind regards

John

Correct. A master that is scanning (or discovering) for other devices is listening not advertising.

  • Owen

Thanks again Owen!

Much appreciated.

Hello Owen, first off, thank you for this great tutorial.

In the swift code, you showed us how to send the data from the iPhone to the Arduino, but is it possible to send data from the Arduino to the app through bluetooth? I am working on a project with sensors and would like my iPhone to receive be constantly receiving the sensor’s readings.

Thank you in advance!

Hi matt425x,
Yes, its definitely possible to send data from the Arduino to the app.

Using the default GATT characteristics on the Black Widow (not sure which BLE module you are using), writing to the UART port from the Arduino causes the Black Widow to transmit on the RX characteristic (uuid A9CD2F86-8661-4EB1-B132-367A3434BC90). If you setup the iOS app to be notified of RX characteristic value changes, then the CBPeripheral’s delegate method didUpdateValueFor is called with the new value(s).

If you want a deeper answer with examples of transmitting analog & digital inputs and controlling outputs, then you may want to check out my book Integrating iOS Bï»żluetooth LE with PIC18 Miï»żcrocontroï»żllers. The book text is in Objective C, but I have the latest Swift 3 version of the code available upon request. Its not Arduino based, but the same coding technics can be used.

  • Owen

@owenb

Can I use HC-06 instead of the Bluetooth Shield you’re using?

If so, what changes (if any) do I need to make to the code?

I’ve never done a Bluetooth project with Arduino or iPhone, so keep the lingo simple. :slight_smile:

The HC-06 is a Bluetooth Classic device and requires completely different code to communicate with. If you want to use this module then you need to use the External Accessories framework.
Just a note, Classic devices require MFi certification from Apple for production. Unless you have large amounts of data to stream (video/audio/etc) I recommend not using Classic and getting a BLE module.

  • Owen

Hi
I am making an app to be used with an Arduino but I am struggling bit. I have only been using Xcode for a short while. I am currently using Xcode 8. The app would be used to turn on and turn off an led light.

The idea is that I press a button and then it shows what bluetooth devices (Hm-10)are available. You can then choose which you would like to connect to.

I would like it so that my app is set up so I have a homepage, in the navigation bar at the top right there would be a connect button. When ‘Connect’ is pressed it goes to the second page where you can choose the bluetooth device to connect to.
Once connected, back on the home page you can press turn on and turn off for the LED light.

The connection to the bluetooth I can do. However I am unable to control it so that when I press the button the table view pops up and you can choose which device to connect to (at the moment it just defaults to choosing one)

Furthermore, how do I set it so that I can choose what info to send to the bluetooth and in turn to the Arduino? In the past I have done this, and I am able to send ASCII characters and that works well for me, but how do i send value from xCode?

My code looks like this at the moment. I only have the one view controller:

import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate
{
    @IBOutlet var BLEStatus: UILabel!
    @IBOutlet var ConnectionLabel: UILabel!
    var manager: CBCentralManager!
    var miband: CBPeripheral!
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        manager = CBCentralManager(delegate: self, queue: nil)
    }
    func centralManagerDidUpdateState(_ central: CBCentralManager)
    {
        var consoleMsg = ""
        switch (central.state)
        {
        case.poweredOff:
            consoleMsg = "BLE is Powered Off"
            BLEStatus.text = "BLE is Powered Off"
        case.poweredOn:
            consoleMsg = "BLE is Powered On"
            manager.scanForPeripherals(withServices: nil, options: nil)
            consoleMsg = "Power On - Scanning for peripherals"
            BLEStatus.text = "Power On - Scanning for peripherals"
        case.resetting:
            consoleMsg = "BLE is resetting"
            BLEStatus.text = "BLE is resetting"
        case.unknown:
            consoleMsg = "BLE is in an unknown state"
            BLEStatus.text = "BLE is in an unknown state"
        case.unsupported:
            consoleMsg = "This device is not supported by BLE"
            BLEStatus.text = "This device is not supported by BLE"
        case.unauthorized:
            consoleMsg = "BLE is not authorised"
            BLEStatus.text = "BLE is not authorised"
        }
        print("\(consoleMsg)")
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
    {
        print("did connect peripheral")
        BLEStatus.text = "did connect peripheral"
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
    {
        print("Peripheral Bluetooth name is: \(peripheral.name)")
        BLEStatus.text = ("Peripheral Bluetooth name is: \(peripheral.name)")
            self.miband = peripheral
            manager.connect(miband, options: nil)
            manager.stopScan()
    }
    
    
    
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
    {
        print("did discover services part")
        BLEStatus.text = "did discover services part"
        if let servicePeripherals = peripheral.services as [CBService]!
        {
            for service in servicePeripherals
            {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }
    
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
    {
        print("did discover charcteristics")
        BLEStatus.text = "did discover charcteristics"
        if let characterArray = service.characteristics as [CBCharacteristic]!
        {
            for cc in characterArray
            {
                if(cc.uuid.uuidString == "0xFFE1")
                {
                print ("discovered some characteristic")
                peripheral.readValue(for: cc)
                }
            }
        }
    }
    
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
    {
        print("did update value part")
        if (characteristic.uuid.uuidString == "FF06")
        {
            let value = characteristic.value!.withUnsafeBytes
                {
                (pointer: UnsafePointer<Int>) -> Int in
                return pointer.pointee
                }
            print("the characteristic is sent over here: (\"(value)")
        }
    }
    
    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Regarding table view, is there a really simple way of doing it?
My understanding so far goes as this:
Create View controller
Add table view
add new file (cocoa class table view controller) and link to the view controller
add relevant code
is that correct? are there any other steps i am missing?

i know this is an awful lot of questions, but I would really appreciate some help

Tej

Hey,

I would like to use the same hardware but instead of control a servo, I want to control relays. How can I integrate UI Switches (for multiple relays) and pass them to the Arduino via Bluetooth LE.

I can’t seem to figure out transmitting the data.

Hi Samra,
For the table view questions, I highly recommend watch Brian Moakley’s video series called “Table Views in iOS”. The videos will get you up and running! If after the videos you have some specific questions on TableViews, I’d be happy to answer them.

Now for the Bluetooth questions. You mentioned that you can successfully connect to the device. This is a good start.
Concerning writing data, in the Arduino Tutorial, take a look at func writePosition(UINT8). It utilizes peripheral.writeValue, which transmits data on the specified characteristic.

I did notice in your code that you are not keeping a reference to the discovered characteristic (inside didDiscoverCharacteristicFor service). I recommend creating a class variable for the discovered characteristics and then you can pass it as a parameter to the writeValue function when its time to write data.

The Arduino tutorial really does what you are asking. I’d download the finished version of the project at take a peek.

Hope this gets you going.
Owen

Hello 17balogj,
The same concepts used for the Servo Slider need applied to the UISwitch. Create an IBAction connection from the UISwitch in the storyboard to your code that is triggered on value change.

Inside the IBAction func, call the function that writes data to your specific characteristic. (Much like the example I gave in the response to Samra’s question). As long as your code has reference to: a) the connected peripheral, b) the discovered characteristic of interest, then you call peripheral.writeValue(data, for: myCoolCharateristic, type: CBCharacteristicWriteType.withResponse). Once this is done, the data is sent via Bluetooth to the connected device.

Let me also recommend my book that goes into detail of handling: inputs (digital/analog), outputs (digital/PWM). The book name is Integrating iOS Bï»żluetooth LE with PIC18 Miï»żcrocontroï»żllers and it can be purchased from my Back-40.com website. To get a preview of the app created in the book, checkout Pad To PIC on the app store. It has cool maintained & momentary switches, turn dials, gauges and input LED indicators!

  • Owen

So I want to tell the arduino to turn on relay 1 if UISwitch 1 is turned on via the iOS app. How do I go about pulling the data from the iOS app onto the arduino. I was thinking I could send a value for each state like 11 = Switch 1 On while 10 = Switch 1 Off. Then I can write a bunch of if statements on the arduino.

How can I pass integers from the swift code to the arduino serial.

This is my code


#include <SoftwareSerial.h>

SoftwareSerial BLE_Shield(4,5); // Cofffigure the Serial port to be off pins D4 and D5. This
// will match the jumpers off the BLE Shield (RX → D4 & TX /> D5).

int relay1 = 0; //Under glow
int relay2 = 1; //Red Accent Lighting on Rigids
int relay3 = 2; //Cube Left
int relay4 = 3; //Cube Right
int relay5 = 6; //Light Bar 50"
int relay6 = 7; //Light Bar 20"
int relay7 = 8; //Horn
int relay8 = 9; //Interior Red Glow

void setup() // Called offly offce per startup
{

for (int i=0; i<4; i++)
{
pinMode(i, OUTPUT);
}

for (int i=6; i<10; i++)
{
pinMode(i, OUTPUT);
}
BLE_Shield.begin(9600); // Setup the serial port at 9600 bps. This is the BLE Shield default baud rate.
}

void loop()
{
if(BLE_Shield.available()) {

  if(BLE_Shield.read() == "relay1.on")
    {
        digitalWrite(relay1, HIGH);
    }
    
  if(BLE_Shield.read() == "relay2.on")
    {
        digitalWrite(relay2, HIGH);
    }

  if(BLE_Shield.read() == "relay3.on")
    {
        digitalWrite(relay3, HIGH);
    }
    
  if(BLE_Shield.read() == "relay4.on")
    {
        digitalWrite(relay4, HIGH);
    }
  
  if(BLE_Shield.read() == "relay5.on")
    {
        digitalWrite(relay5, HIGH);
    }
    
  if(BLE_Shield.read() == "relay6.on")
    {
        digitalWrite(relay6, HIGH);
    }

  if(BLE_Shield.read() == "relay7.on")
    {
        digitalWrite(relay7, HIGH);
    }
    
  if(BLE_Shield.read() == "relay8.on")
    {
        digitalWrite(relay8, HIGH);
    }
  if(BLE_Shield.read() == "relay1.off")
    {
        digitalWrite(relay1, LOW);
    }
    
  if(BLE_Shield.read() == "relay2.off")
    {
        digitalWrite(relay2, LOW);
    }

  if(BLE_Shield.read() == "relay3.off")
    {
        digitalWrite(relay3, LOW);
    }
    
  if(BLE_Shield.read() == "relay4.off")
    {
        digitalWrite(relay4, LOW);
    }
  
  if(BLE_Shield.read() == "relay5.off")
    {
        digitalWrite(relay5, LOW);
    }
    
  if(BLE_Shield.read() == "relay6.off")
    {
        digitalWrite(relay6, LOW);
    }

  if(BLE_Shield.read() == "relay7.off")
    {
        digitalWrite(relay7, LOW);
    }
    
  if(BLE_Shield.read() == "relay8.off")
    {
        digitalWrite(relay8, LOW);
    }
}

}

Summary

This text will be hidden

As I mentioned in the previous response, just call the func writePosition(_ position: UInt8) used in the tutorial and pass a UInt8 as the position parameter.
i.e. writePosition(position: 10) or writePosition(position: 11)

I am getting two errors when I try and accomplish this here are both examples.

The first screenshot error is because the userInfo is being forced unwrapped to a [String: Bool] but something else is being sent to the notification handler.
Either change to the code so its not force unwrapping and/or fix the code that is sending the wrong data type to the notification.
i.e.:
if let userInfo = (notification as NSNotification).userInfo as? [String: Bool] {
// Rest of func contents

}

func writePosition() is a func of a BTService instance, therefore you can’t call it directly. Which is the case for all functions in general.

Use btDiscoverySharedInstance.bleService.writePosition(11).

This is shown in the tutorial under the Starter Project Overview section, inside func sendPosition(_ position: UInt8).

Hi Owen
I had a look at the table views and also at what you have done and I have a better understanding what is going on.
I can make a simple table view with a pre made array. However I am having problems with bluetooth listings and connections now.

Currently I have this for the table:

//number of sections
func numberOfSections(in tableView: UITableView) → Int
{
return 1
}

// number of rows in each section
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) → Int
{
return peripherals.count
}

the sections and rows is very straightforward
the variable peripheral I have copied what you had done and I have:

var peripherals: [(peripheral: CBPeripheral, RSSI: Float)] = []

However this bit I do not understand and I believe it is this bit that is naming the rows by the bluetooth names:

// cell names based off peripherals
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
    let label = cell.viewWithTag(1) as! UILabel!
    label?.text = peripherals[(indexPath as NSIndexPath).row].peripheral.name
    return cell
}

I have also gone a bit further to try connecting:

//select a connection and it will take you back to the main screen
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
    tableView.deselectRow(at: indexPath, animated: true)
    serial.stopScan()
    selectedPeripheral = peripherals[(indexPath as NSIndexPath).row].peripheral
    serial.connectToPeripheral(selectedPeripheral!)
    
}

might also add

var selectedPeripheral: CBPeripheral?

So to summarise I have the table view working to some limit. I understand that to get the rows working I link it to the number of peripherals there are. That makes sense.
To list the names of the peripherals you create an array and you can then add that to the fund cellForRowAt. However it does not seem to be working for me and it is driving me nuts.

when looking at yours I don’t understand why do you have separate classes for BTService and BTDiscovery?

Basically, all in all I cant seem to get a list of bluetooth devices even after looking through yours.
All help is appreciated!!