Feb.01

Interfacing TI SensorTag with iOS Devices using Swift

Interfacing TI SensorTag with iOS Devices using Swift

In this post, I will show how to connect an iOS device (with Bluetooth 4.0) to the SensorTag, read data from the sensors and display it on the screen. For the purpose of this tutorial you will need:

  • An iOS device with Bluetooth 4.0
  • iOS Developer Account
  • Texas Instruments SensorTag
  • Xcode 6.1

The SensorTag from Texas Instruments is a fantastic little kit with multiple sensors that transmit data via Bluetooth Low Energy. It has six sensors but I will be showing the use of only one as an example here. You can find my complete project with code for other sensors on GitHub.

Getting Started

Fire up Xcode and start a new project with Single View Application template.

Setting up the View (or storyboard)

I am not going to use the storyboard here because I like setting up my view in code. But if you prefer the storyboard method feel free to do so.

We need three UILabels:

  • titleLabel – This will be used as the label for displaying the title
  • statusLabel – Just below the titleLabel to show what is happening in terms of connectivity
  • tempLabel – This will just display the temperature value read from the SensorTag

To set this up in code, go to your ViewController.swift and declare three variables of UILabel class as follow:

var titleLabel : UILabel!
var statusLabel : UILabel!
var tempLabel : UILabel!

The next step is to set up each of these labels and add them as a subview to the main view. Inside the viewDidLoad() function, add the following chunk of code:

// Set up title label
titleLabel = UILabel()
titleLabel.text = "My SensorTag"
titleLabel.font = UIFont(name: "HelveticaNeue-Bold", size: 20)
titleLabel.sizeToFit()
titleLabel.center = CGPoint(x: self.view.frame.midX, y: self.titleLabel.bounds.midY+28)
self.view.addSubview(titleLabel)

// Set up status label
statusLabel = UILabel()
statusLabel.textAlignment = NSTextAlignment.Center
statusLabel.text = "Loading..."
statusLabel.font = UIFont(name: "HelveticaNeue-Light", size: 12)
statusLabel.sizeToFit()
statusLabel.frame = CGRect(x: self.view.frame.origin.x, y: self.titleLabel.frame.maxY, width: self.view.frame.width, height: self.statusLabel.bounds.height)
self.view.addSubview(statusLabel)

// Set up temperature label
tempLabel = UILabel()
tempLabel.text = "00.00"
tempLabel.font = UIFont(name: "HelveticaNeue-Bold", size: 72)
tempLabel.sizeToFit()
tempLabel.center = self.view.center
self.view.addSubview(tempLabel)

All that is happening in the code above is that, one at a time, each UILabel is being initialised, its font is set and the label’s position in the frame defined. The end result will look as shown below.

SwiftSensorTagView

The CoreBluetooth Framework

Import the CoreBluetooth Framework in your project by adding the following statement to the top. This provides access to all the classes and methods needed to access the Bluetooth radio on an iOS device.

import CoreBluetooth

The ViewController must conform to the CBCentralManagerDelegate and CBPeripheralDelegate protocols. The former allows monitoring and discovery of BLE peripheral devices and methods to call when a connection is established or dropped. The latter allows discovering the services and characteristics of a peripheral device (sensor) and get updated values from it. To conform to these protocols, add them to the class declaration line of ViewController. It should look like this:

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate

Add the two lines of code below to declare two properties of the ViewController. CBCentralManager object is used to manage the discovered sensors. In this case that will be a SensorTag (also known as a peripheral) and represented by the CBPeripheral object.

// BLE
var centralManager : CBCentralManager!
var sensorTagPeripheral : CBPeripheral!

Initialize the centralManager inside viewDidLoad() as follows by setting self as its delegate.

// Initialize central manager on load
centralManager = CBCentralManager(delegate: self, queue: nil)

At this point we have set up all the properties we need and the user interface. The next step is to implement the delegate methods for both CBCentralManagerDelegate and CBPeripheralDelegate. Before that, lets take a very quick overview on what we are actually trying to achieve and how it will be done.

Before scanning for BLE peripherals in the vicinity we should check whether Bluetooth on the hardware (iPhone/iPad) is switched on and is available for use. Once we can confirm that we start scanning. What happens during the scan? The peripherals keep on transmitting small bytes of advertisement data which allows the central device to identify them. So when we start scanning, a delegate method gets called for each peripheral device found. What we have to do is check the device’s advertisement data to identify it. Once we have identified our desired peripheral, we stop scanning and issue a command to connect to this peripheral. On establishing a connection, we query the sensor for a list of its services. In this case, for example, the SensorTag has service profiles for IR Temperature, Accelerometer, etc. For the service that we need, we then discover its characteristics, such as enable register, data value, time interval, etc. Finally we will enable the sensor to start sensing and subscribe to its notifications such that we receive a data value after a set time period.

This was a very quick summary of what we are going to implement next. To understand the Core Bluetooth framework in more detail, have a look at his fantastic tutorial: http://www.raywenderlich.com/52080/introduction-core-bluetooth-building-heart-rate-monitor.

Define BLE Constants

The BLE peripheral services and characteristics are identified using their UUIDs. For SensorTag, these are defined in the user guide. Define these as constant CBUUID objects and we will be using them later.

// IR Temp UUIDs
let IRTemperatureServiceUUID = CBUUID(string: "F000AA00-0451-4000-B000-000000000000")
let IRTemperatureDataUUID   = CBUUID(string: "F000AA01-0451-4000-B000-000000000000")
let IRTemperatureConfigUUID = CBUUID(string: "F000AA02-0451-4000-B000-000000000000")

Now lets implement the delegate methods to perform each of the aforementioned functions. We will also update the statusLabel so that the status is shown in the view whenever each function gets called.

Check the BLE Hardware Status

This is done using  centralManagerDidUpdateState delegate method. If the state of CBCentralManager object is PoweredOn we will start scanning for peripheral devices and update the statusLabel text to show the same. The CBCentralManager state can be PoweredOff or Unknown or even Unsupported so feel free to have a different error message for each if you need.

// Check status of BLE hardware
func centralManagerDidUpdateState(central: CBCentralManager!) {
    if central.state == CBCentralManagerState.PoweredOn {
        // Scan for peripherals if BLE is turned on
        central.scanForPeripheralsWithServices(nil, options: nil)
        self.statusLabel.text = "Searching for BLE Devices"
    }
    else {
        // Can have different conditions for all states if needed - print generic message for now
        println("Bluetooth switched off or not initialized")
    }
}

Discover Peripherals To Find SensorTag

Now we check the advertisement data of each peripheral that the central manager finds. This is done using the didDiscoverPeripheral delegate method which gets called for every peripheral found. Implement the method as below.

// Check out the discovered peripherals to find Sensor Tag
func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {

    let deviceName = "SensorTag"
    let nameOfDeviceFound = (advertisementData as NSDictionary).objectForKey(CBAdvertisementDataLocalNameKey) as? NSString

    if (nameOfDeviceFound == deviceName) {
        // Update Status Label
        self.statusLabel.text = "Sensor Tag Found"

        // Stop scanning
        self.centralManager.stopScan()
        // Set as the peripheral to use and establish connection
        self.sensorTagPeripheral = peripheral
        self.sensorTagPeripheral.delegate = self
        self.centralManager.connectPeripheral(peripheral, options: nil)
    }
    else {
        self.statusLabel.text = "Sensor Tag NOT Found"
    }
}

The TI SensorTag is identified by checking the object for its local name key.  The advertisementData is an NSDictionary Object and we look for the NSString value at the key CBAdvertisementDataLocalNameKey (which is an optional since it may or may not exist). If the value is SensorTag, the statusLabel is updated to indicate that a SensorTag is found and the scanning for devices is stopped. The found peripheral is then set as the CBPeripheral object we will use and set its delegate as self before attempting to connect to the peripheral.

Connection to a Peripheral

The delegate method didConnectPeripheral gets called when a successful connection with the peripheral is established. On connection, we will call the peripheral method discoverServices() and update the statusLabel text as shown below.

// Discover services of the peripheral
func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) {
    self.statusLabel.text = "Discovering peripheral services"
    peripheral.discoverServices(nil)
}

Discovering Peripheral Services

The services of a peripheral can be identified by their UUIDs. The different service UUIDs for SensorTag can be found here and also in the user guide. We will check the UUID of each service of the peripheral and compare it against IRTemperatureServiceUUID which we defined earlier. When the service is found we will explore its characteristics. You can try and print all the different service UUIDs in the console to see what other services are being offered by the peripheral by uncommenting the last line of the code below.

// Check if the service discovered is a valid IR Temperature Service
func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) {
    self.statusLabel.text = "Looking at peripheral services"
    for service in peripheral.services {
        let thisService = service as CBService
        if service.UUID == IRTemperatureServiceUUID {
            // Discover characteristics of IR Temperature Service
            peripheral.discoverCharacteristics(nil, forService: thisService)
        }
        // Uncomment to print list of UUIDs
        //println(thisService.UUID)
    }
}

 Discovering Characteristics and Enabling Sensor

In the above method, we called the discoverCharacteristic() function when IR Temperature service was found. As a result the didDiscoverCharacteristicsForService method of CBPeripheralDelegate protocol gets called. The code below shows how to implement this delegate method.

// Enable notification and sensor for each characteristic of valid service
func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) {

    // update status label
    self.statusLabel.text = "Enabling sensors"

    // 0x01 data byte to enable sensor
    var enableValue = 1
    let enablyBytes = NSData(bytes: &enableValue, length: sizeof(UInt8))

    // check the uuid of each characteristic to find config and data characteristics
    for charateristic in service.characteristics {
        let thisCharacteristic = charateristic as CBCharacteristic
        // check for data characteristic
        if thisCharacteristic.UUID == IRTemperatureDataUUID {
            // Enable Sensor Notification
            self.sensorTagPeripheral.setNotifyValue(true, forCharacteristic: thisCharacteristic)
        }
        // check for config characteristic
        if thisCharacteristic.UUID == IRTemperatureConfigUUID {
            // Enable Sensor
            self.sensorTagPeripheral.writeValue(enablyBytes, forCharacteristic: thisCharacteristic, type: CBCharacteristicWriteType.WithResponse)
        }
    }

}

First we update to statusLabel for some visual benefit and then define 0x01 as a constant. Then we need to enable the sensor notification for the data characteristic and enable the sensor itself by writing to the config characteristic. More details on this can be found in the SensorTag user guide.

To enable the IR Temperature sensor we need to find the IR Temperature Config service (identifiable via its UUID) and then write a value of 0x01 to it. Once this is done the next delegate method gets called every time there is an updated data value to read from the sensor.

Getting Data Values from the Sensor

Now that we have subscribed to the sensor notifications (for data characteristic) the didUpdateValueForCharacteristic delegate method gets called every time we have to read a value from the sensor. We first check if this is a valid IR Temperature data characteristic (using its UUID) and read the value from this characteristic. For IR Temperature service this data is in the form:

ObjectLSB:ObjectMSB:AmbientLSB:AmbientMSB

For the ambient temperature we the corresponding 16 bits. So for example we get a value of 0xADFF800B the raw value will be written as 0B80 which needs to be divided by 128 to get the ambient temperature. For calculating the object temperature and other sensor values refer to the user guide where all algorithms are nicely explained. If you want these in Swift, refer to my Github project SwiftSensorTag which has most of these algorithms implemented in Swift.

For IR Temperature data characteristic, we therefore convert the bytes to signed 16 bit array of integers, divide the value by 128 which is the ambient temperature and display on the main view by assigning the value to tempLabel.

// Get data values when they are updated
func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {

    self.statusLabel.text = "Connected"

    if characteristic.UUID == IRTemperatureDataUUID {
        // Convert NSData to array of signed 16 bit values
        let dataBytes = characteristic.value
        let dataLength = dataBytes.length
        var dataArray = [Int16](count: dataLength, repeatedValue: 0)
        dataBytes.getBytes(&dataArray, length: dataLength * sizeof(Int16))

        // Element 1 of the array will be ambient temperature raw value
        let ambientTemperature = Double(dataArray[1])/128

        // Display on the temp label
        self.tempLabel.text = NSString(format: "%.2f", ambientTemperature)
    }
}

That’s about it really. One more delegate method which would be nice to implement is to define the behaviour when the peripheral gets disconnected.

Disconnection From a Peripheral

This is a very simple method and all it does is that when SensorTag is disconnected, for whatever reason, it updates the statusLabel to say that is the case and then starts scanning again for BLE devices.

// If disconnected, start searching again
func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!) {
    self.statusLabel.text = "Disconnected"
    central.scanForPeripheralsWithServices(nil, options: nil)
}

Build and Run

Build and run the project on an actual iOS device and, if you have followed everything carefully, you should see something like this on your device (screenshot from an iPhone 6).

SwiftSensorTagDemo

You can download the complete source file for this project here. If you encounter any problems with this please feel free to ask in the comment section.

Useful Links

  1. SwiftSensorTag on Github: Check out this project for a complete implementation of SensorTag interface using Swift
  2. SensorTag User Guide: http://processors.wiki.ti.com/index.php/SensorTag_User_Guide
  3. SensorTag Attributes Table (for all the UUIDs): http://processors.wiki.ti.com/images/a/a8/BLE_SensorTag_GATT_Server.pdf
  4. Introduction To Core Bluetooth on RayWenderlich for a great tutorial and understanding of CB framework
Uncategorized
Share this Story:
  • facebook
  • twitter
  • gplus

About anasimtiaz

Comments(68)

  1. Ravi
    864 days ago

    I am getting “Bluetooth switched off or not initialized”, my LMP version is 0x6. Please let me know if you ran into this issue.Thanks!

  2. Ravi
    864 days ago

    Output values are 2 and 5 for NSLog(“%i”,central.state.rawValue) NSLog(“%i”,CBCentralManagerState.PoweredOn.rawValue) respectively.

    • anasimtiaz
      864 days ago

      central.state corresponds to Unsupported … what device are you using?

      • zillarelli
        816 days ago

        i’m assuming he’s using a simulator, none of which support bluetooth currently

  3. sri
    817 days ago

    Great tutorial. How to do this: scan, connect, ask for command. 00, 01: ask user for input. 10: print ambTemp on screen. 11: it prints objetemp on screen.

  4. Piboo
    805 days ago

    Why the first element of your dataArray corresponds to the ambient temperature ? It is build from 2 bytes

    • anasimtiaz
      805 days ago

      It is Int16, so two bytes. Not sure I understand the question.

      • Piboo
        805 days ago

        Sorry the message can not be too long. I’m really new and I think I have a misunderstood of type conversion.

        • Piboo
          805 days ago

          So as Int16 is two bytes it will take the first two bytes at the index 0 of dataArray and the last two bytes at index 1. That’s it ?

          • Piboo
            805 days ago

            But how is made the conversion ? I was thinking to calculate like this : AmbLSB + AmbLSB *256. Am I wrong ?

          • anasimtiaz
            803 days ago

            The ambient temperature value is stored at the first index the way getBytes work. And then you need to divide that by 128.

  5. Nathan Roberton
    803 days ago

    I’m attempting to follow these instructions, but for me the didDiscoverPeripheral delegate method never gets called. Any ideas why this wouldn’t be calling, or how to go about troubleshooting it?

    • anasimtiaz
      803 days ago

      If the peripheral is out of range or not transmitting or just switched off then this method will not get called.

  6. Nathan Roberton
    802 days ago

    I don’t think that’s it. I am communicating with the device with an app called LightBlue and it works fine there.

  7. Nathan Roberton
    802 days ago

    Update: I get the events when I modify your code instead of following the tutorial. I can’t figure out what I did wrong. The code builds and everything.

    • anasimtiaz
      802 days ago

      Would be interesting to know what modification is making it work for you.

  8. Anup
    798 days ago

    Porting to Mac. On the line in “func peripheral……”:
    let dataLength = dataBytes.length
    Error:
    ‘() -> NSData!’ does not have a member named ‘length’

    Any pointers?

  9. Anup
    797 days ago

    Also, your example is for “ambient temperature”. The IR temperature needs some work to calculate.

    • anasimtiaz
      797 days ago

      Not sure about Mac. Also I am aware of the temp example. The code on SensorTag website easily allows you to get the IR temp.

  10. Rico
    792 days ago

    hey i was wondering is there any way to emulate this device CC2640 and pass in the data as if I was that device to the IOS APP?

  11. sri
    778 days ago

    How to add another step of “choosing the device to connect” (list all devices)?

    • anasimtiaz
      778 days ago

      Yes, pretty straightforward. When you’re scanning for the device get the complete list and display it in a tableview

  12. Dan Phelan
    772 days ago

    Thanks for making this guide. It has been very helpful in learning Swift and the TI BLE sensor tag.

    Dan

  13. Bill
    762 days ago

    Thanks. Question: How to set the sampling rate ?

  14. Bill
    762 days ago

    For example, whenever sensortag connect to the App, its sampling rate is configured by the App. // Or, Use a mother board, code the sensortag from a firmware level to write a fixed sampling rate.

  15. Mark
    759 days ago

    UUIDs for magnetic, gyro and accel are not present when uncommenting “//println(thisService.UUID)”, so can’t get characteristics of these – used these?

  16. Mohammad
    752 days ago

    Thanks a lot! very helpful!

  17. Simon Tooke
    694 days ago

    The swift code was very helpful. I don’t suppose you’d consider a more liberal license? I ported it to OS X.

    • anasimtiaz
      694 days ago

      I don’t mind. What license do you recommend?

      • Simon Tooke
        688 days ago

        How about the MIT license? derivative works have to give you credit, and not hold you liable.
        http://opensource.org/licenses/MIT

        • anasimtiaz
          688 days ago

          Thanks. Done.

          • Simon Tooke
            633 days ago

            Thank you – when I have time, I’ll upload my code to github.

  18. Ken
    690 days ago

    Wow. Awesome, thanks so much.

    Add the CoreBluetooth.framework by selecting Project Name > Build Phases > Link Binary With Libraries > + > search and select “CoreBluetooth.framework”

  19. Bill
    689 days ago

    Thank you so much Anasimtiaz for sharing !!
    Any suggestions on Swift CC2650 ?

  20. Paul
    682 days ago

    I’ve followed this for the CC2560 (I wrote an Obj-c app for the older ST some time ago) but find the didUpdateValueForCharacteristic never gets called?

    Any suggestions?

    • anasimtiaz
      680 days ago

      Do you have your code somewhere? Hard to say without looking at it

  21. Zhao
    654 days ago

    Hi, just wondering if you have done something with 2650?

  22. Roberto
    654 days ago

    I’m refactoring the code for 2650.

    • anasimtiaz
      654 days ago

      Great! Please share the link once you’re ready with it.

    • Swift
      640 days ago

      yes. please share Sensortag 2.0…. Many thanks

    • Hermia
      589 days ago

      Could you please share the code for 2650? Many thanks.

  23. Pierre
    641 days ago

    Hi, In Xcode7.1, …discoverServices() gets called, but returns 0x0 services. the code does not trigger didDiscoverServices(). Full code here http://pastie.org/private/vqv4ukvhb3nmwwndtnxxq

  24. Mario
    627 days ago

    Super useful!
    Do you plan to update the code? It doesn’t work in XCode 7.1

    • anasimtiaz
      627 days ago

      Wasn’t planning to but got few requests to do it so will try in couple of weeks.

    • anasimtiaz
      616 days ago

      Updated for XCode 7.1 (Swift 2).

  25. Kitson
    624 days ago

    Hey, I’m trying to activate the movement sensors but I’m really struggling. At which point so you activate them? Hope you can help! Thanks

  26. Bill
    623 days ago

    Gyroscope values are zero for Axis X and Z.
    Only Gyro-Y is non-zero.
    Anyone has the same problem ? Please point. Thanks a lot.

    • Vito
      601 days ago

      Hy Bill, you have to enable all three axis when you check the validConfigCharacteristic. The right value to notify in bytes is 7.

  27. Wesley
    606 days ago

    Hi Sir,Do you considered the OAD(over air download) using swift.

  28. Kalyn
    518 days ago

    Hi, I just get “Sensor Tag NOT Found” even though my sensor tag is on. Any suggestions on how to fix this? Could this be due to the fact that I’m using the Sensor Tag 2.0?

  29. Gabe
    486 days ago

    THANK YOU SO MUCH! I’ve been working on this for at least two days! The missing part I had was enabling the sensor for the Config UUID AND enabling the notification for the Data UUID!

  30. Travis Spire-Sweet
    444 days ago

    Could you please help me understand how to convert this one section from SWIFT to Objective X ? The // Convert NSData to array of signed 16 bit values. Thank you so much for this tutorial!

  31. frank
    417 days ago

    I get an Error : “ViewController does not conform to protocol ‘CBCentralManagerDelegate’ “.

    • anasimtiaz
      416 days ago

      Are you using the updated project from GitHub for Swift 2?

  32. frank
    416 days ago

    Hi Anasimtiaz,
    Thanks for the response. I am not using the updated project from GitHub for Swift2. I am copying the text from this blog.

    • anasimtiaz
      415 days ago

      This is probably a bit out of date. The version on GitHub works fine.

  33. Rajeev
    192 days ago

    Hi Anasimtiaz,
    Could you provide a sample as to how can I read and display sensor tag ID and RSSI value without connecting to the sensor tag from the iPhone. Thanks

  34. Steve
    152 days ago

    In Xcode 8.3 beta 2, Swift 3.0, made modifications for Swift versions, using a ST 2541. No data is returned.

  35. Steve
    152 days ago

    Changed line :
    let enableBytes = Data(buffer: UnsafeBufferPointer(start: &enableValue, count: MemoryLayout.size))

  36. Steve
    152 days ago

    to: var intVal = 1
    let enableBytes = withUnsafePointer(to: &intVal){
    return Data(bytes: $0, count: MemoryLayout.size)
    }

  37. Steve
    152 days ago

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {

    is never called.

  38. Steve
    151 days ago

    Ignore…got it working! BUT: accelerometer data is showing .027 when that tag is laying flat. Shouldn’t it be -1? What units are you using?

    • Oli
      137 days ago

      Hi Steve, are you able to share the changes you made to have it work with Swift 3?

      Thank you!

  39. hassine
    131 days ago

    func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {}
    this method not work in swift 3, any ?

Leave a comment

Comment