Creating a Temperature Sensor for iOS Using BLE and Arduino

Today we’ll create something cool, a temperature sensor and an iOS app that connects to it. For this project, we’ll be using an Arduino with a BLE breakout circuit. On the iOS side, we’ll be using CoreBluetooth. Don’t worry if some of these things are unfamiliar to you, I’ll try to give you step-by-step instructions on how to create this. We’ll be sending a string instead of a float with temperature using the BLE, this way, you can reuse the circuit for some other projects you might derive out of this one. In the first part of this post, we’ll talk about the Arduino, and in the second part about the iOS app. So let’s get started.

Arduino

For this project to work, you’ll need an Arduino, there are many versions of it, but I would recommend you get the official starter pack from amazon, check it out here. In this pack, you’ll have pretty much everything you’ll need to start working with it, along with a nice example book. If you’re interested in learning more about Arduino, and how to use it, Arduino for Dummies is a great book to start you off.

Another piece of hardware you’ll need is a BLE breakout circuit by Adafruit. You could use any other circuit, but I used this one, and the Arduino sketch is written for this circuit.

Bluetooth Low Energy

BLE stands for Bluetooth Low Energy, sometimes called Bluetooth Smart, or Bluetooth 4.1. It’s a Bluetooth standard that’s using very low energy to transmit small chunks of data over short distances. It’s very popular in the IoT projects, it allows you to create a local network of connected devices. BLE is very different than your standard Bluetooth, and if you want to learn more about it, I would recommend a book called Getting Started with Bluetooth Low Energy.

BLE is very structured, you have your peripheral (the device), which can contain many services, a service can contain many characteristics. Each of these is identified by a UUID. Characteristics can be notifying. What this means is, every time their value changes, they notify the observer (iOS app, in our case). We’ll use this in our project, we’ll be writing the temperature data in our characteristic, and it will be read automatically by our iOS app.

You can see this depicted in the following diagram:

Arduino Hardware

Let’s hook up the hardware. If you got the same kit as I do, you can go to Adafruit website, and follow the instructions on how to wire the BLE to your Arduino. This is the wiring diagram from their website:

Image source: learn.adafruit.com

You’ll also need a temperature sensor. If you bought the kit I recommended you’ll find the diagram with the example code in the book included in the kit. If you didn’t, that’s ok, just wire the sensor output, to the analog A0 input on the Arduino.

This is my setup:

Now that we have everything wired up, let’s write some Arduino code.

Arduino Software

To write software for Arduino you’ll need to install Arduino IDE, it’s available for Mac, so you can just go to their website, and download it. They have a great guide on their website on how to set up your IDE. Once you installed the IDE, you’ll need to install the Adafruit libraries. Adafruit has a great tutorial on how to do this, so go and check it out.

Now we have the hardware wired up, and all the libraries we need. The only thing that’s left is to write some code and upload it to our Arduino board. The code is not that long, so I’ll just show the whole source file here:

#include <SPI.h>
#include "Adafruit_BLE_UART.h"

#define ADAFRUITBLE_REQ 10
#define ADAFRUITBLE_RDY 2
#define ADAFRUITBLE_RST 9

Adafruit_BLE_UART BTLEserial = Adafruit_BLE_UART(ADAFRUITBLE_REQ, ADAFRUITBLE_RDY, ADAFRUITBLE_RST);

const int numReadings = 50;
const int initialValue = 144;

int readings[numReadings];
int readIndex = 0;
int total = initialValue * numReadings;

const int sensorPin = A0;

void setup() {
  Serial.begin(9600);

  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = initialValue;
  }
  
  BTLEserial.setDeviceName("BLETemp");
  BTLEserial.begin();
}

aci_evt_opcode_t laststatus = ACI_EVT_DISCONNECTED;

void loop() {
  BTLEserial.pollACI();

  aci_evt_opcode_t status = BTLEserial.getState();
  if (status != laststatus) {
    laststatus = status;
  }

  if (status == ACI_EVT_CONNECTED) {
    String temperatureString = averageTemperature();
    
    uint8_t sendbuffer[20];
    temperatureString.getBytes(sendbuffer, 20);
    char sendbuffersize = min(20, temperatureString.length());

    Serial.print(F("\n* Sending -> \"")); Serial.print((char *)sendbuffer); Serial.println("\"");

    BTLEserial.write(sendbuffer, sendbuffersize);
  }
}

String averageTemperature() {
  int average = averageValue(sensorPin);
  return temperature(average);
}

int averageValue(int inputPin) {
  total = total - readings[readIndex];
  readings[readIndex] = analogRead(inputPin);
  total = total + readings[readIndex];
  readIndex = readIndex + 1;

  if (readIndex >= numReadings) {
    readIndex = 0;
  }
  
  return total / numReadings;
}

String temperature(int sensorVal) {
  float voltage = (sensorVal / 1024.0) * 5.0;
  float temperature = (voltage - .5) * 100;
  Serial.println(temperature);
  return String(temperature);
}

In this code, we’re handling two things. BLE breakout circuit and the temperature sensor. Let’s go over the BLE code first.

At the top of the class, we create an instance of the BLE UART with the corresponding pins. In the setup method, we set the device name, and start our BLE chip. On Arduino, the setup method is executed once when the Arduino is started, and the loop method is executed constantly. So all our processing is done within the loop method. In this method, we get the current state of the BLE, and if it’s connected we send our temperature data. We can only send 20 bytes at a time, and once we get our temperature data (formatted as a string), we write it into our object. This will be our notifying RX characteristic that our iOS app will read from.

Our temperature sensor is connected to the analog A0 input, and since we’ll be sampling the data at a very high frequency we need to smooth out the data, in order to prevent spiking. To smooth the data we’ll be using the ‘averageValue’ function. This function will take the last 50 measurements and give you an average value. We’ll be sampling the data many times a second, so this will give us a smooth sample. Once we get the smoothed out data, we need to convert the raw sensor value to the actual temperature. We’ll do this in the ‘temperature’ method. This method will take the sensor input value, convert it to the voltage, and then into the temperature. This string is what we’re writing into our BLE RX characteristic.

That’s pretty much it on the Arduino side, we have a piece of hardware that’ s transmitting sensor data over BLE. The only thing left to do is to read that data in the iOS app.

iOS Project

Once you create a new project got to your project capabilities tab, enable ‘Background Modes’ and select ‘Uses Bluetooth LE accessories’ in order for this to work, like so:

BLEManager

Now we’re ready to start coding. We’ll only need one class to hook everything up. We’ll call this class ‘BLEManager’, and it will be around 200 lines of code. That’s all we need. Amazing, right 🙂 Obviously we’ll be using the Core Bluetooth, so we need to import that.

This is the core of the class:

class BLEManager: NSObject, BLEManagable {

    fileprivate var shouldStartScanning = false
    
    private var centralManager: CBCentralManager?
    private var isCentralManagerReady: Bool {
        get {
            guard let centralManager = centralManager else {
                return false
            }
            return centralManager.state != .poweredOff && centralManager.state != .unauthorized && centralManager.state != .unsupported
        }
    }
    
    fileprivate var connectingPeripheral: CBPeripheral?
    fileprivate var connectedPeripheral: CBPeripheral?
    
    fileprivate var delegates: [Weak<AnyObject>] = []
    fileprivate func bleDelegates() -> [BLEManagerDelegate] {
        return delegates.flatMap { $0.object as? BLEManagerDelegate }
    }
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background))
        startScanning()
    }
    
    func startScanning() {
        guard let centralManager = centralManager, isCentralManagerReady == true else {
            return
        }
        
        if centralManager.state != .poweredOn {
            shouldStartScanning = true
        } else {
            shouldStartScanning = false
            centralManager.scanForPeripherals(withServices: [BLEConstants.TemperatureService], options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
        }
    }
    
    func stopScanning() {
        shouldStartScanning = false
        centralManager?.stopScan()
    }
    
    func addDelegate(_ delegate: BLEManagerDelegate) {
        delegates.append(Weak(object: delegate))
    }
    
    func removeDelegate(_ delegate: BLEManagerDelegate) {
        if let index = delegates.index(where: { $0.object === delegate }) {
            delegates.remove(at: index)
        }
    }
}

The main method here is ‘startScanning’. We call this method right from our constructor. After we do some sanity checks, we start scanning for peripherals that have a service with a specific UUID. That would be our temperature service.

Once we start scanning for services, we’ll wait for our callback from the central manager. Here are the delegate methods that we’ll implement:

// MARK: CBCentralManagerDelegate
extension BLEManager: CBCentralManagerDelegate {
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            if self.shouldStartScanning {
                self.startScanning()
            }
        } else {
            self.connectingPeripheral = nil
            if let connectedPeripheral = self.connectedPeripheral {
                central.cancelPeripheralConnection(connectedPeripheral)
            }
            self.shouldStartScanning = true
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        self.connectingPeripheral = peripheral
        central.connect(peripheral, options: nil)
        self.stopScanning()
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        self.connectedPeripheral = peripheral
        self.connectingPeripheral = nil
        
        peripheral.discoverServices([BLEConstants.TemperatureService])
        peripheral.delegate = self
        
        self.informDelegatesDidConnect(manager: self)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        self.connectingPeripheral = nil
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        self.connectedPeripheral = nil
        self.startScanning()
        self.informDelegatesDidDisconnect(manager: self)
    }
}

In the ‘didDiscover’ callback we connect to the discovered peripheral. It’s important to note here that we need to keep a strong reference to the peripheral we’re trying to connect to (connectingPeripheral property) since it’s not retained. And in the ‘didConnect’ delegate callback we discover available services for our TemperatureService, we’ll set our peripheral delegate here, so we can read data from our RX characteristic. In the ‘centralManagerDidUpdateState’ we’re starting a scan if we started a scan while the Bluetooth was powered off. Also, if we power the Bluetooth off we cancel the peripheral connection and raise the flag to start scanning when the device powers back up. This method will get called when you power off the Bluetooth on your device, or on the Arduino.

We used central manager delegate to start the service discovery, we’ll use the peripheral delegate callbacks to discover our characteristic, and read data from it. Here are the callbacks that we’ll implement:

// MARK: CBPeripheralDelegate
extension BLEManager: CBPeripheralDelegate {
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let tempService = peripheral.services?.filter({ $0.uuid.uuidString.uppercased() == BLEConstants.TemperatureService.uuidString.uppercased() }).first {
            peripheral.discoverCharacteristics([BLEConstants.RXCharacteristic], for: tempService)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let rxCharacteristic = service.characteristics?.filter({ $0.uuid.uuidString.uppercased() == BLEConstants.RXCharacteristic.uuidString.uppercased()}).first {
            peripheral.setNotifyValue(true, for: rxCharacteristic)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let temperatureData = characteristic.value else {
            return
        }
        
        if let dataString = NSString.init(data: temperatureData, encoding: String.Encoding.utf8.rawValue) as? String {
            self.informDelegatesDidReceiveData(manager: self, dataString: dataString)
        }
    }
}

When the peripheral delegate get’s called with the discovered services, we’ll filter the array to find our temperature service, and then we’ll start the characteristics discovery for our RX characteristic. When the characteristics are discovered, we’ll set the notify value to true for the RX characteristic. This way we’ll get notified every time Arduino writes something into the RX characteristic.

Now every time the characteristic updates, ‘didUpdateValueFor’ will get called. We’ll read the data from the .value property, and convert it into a string. The last thing left to do is to inform the delegate with the temperature string, and that’s it.

View Controller

We’ll keep the view controller simple, just a label with a temperature value. This is the whole view controller:

class ViewController: UIViewController {

    var bleManager: BLEManagable = BLEManager()
    
    @IBOutlet weak var temperatureLabel: UILabel!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        bleManager.addDelegate(self)
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        bleManager.removeDelegate(self)
    }
}

// MARK: BLEManagerDelegate
extension ViewController: BLEManagerDelegate {
    
    func bleManagerDidConnect(_ manager: BLEManagable) {
        self.temperatureLabel.textColor = UIColor.black
    }
    func bleManagerDidDisconnect(_ manager: BLEManagable) {
        self.temperatureLabel.textColor = UIColor.red
    }
    func bleManager(_ manager: BLEManagable, receivedDataString dataString: String) {
        self.temperatureLabel.text = dataString + "℃"
    }
}

In the ‘viewWillAppear’ we add the view controller as a delegate, and in the ‘viewDidDisappear’ we remove the view controller as the delegate. In the delegate callbacks, we change the colour of the temperature label. Red if Arduino is not connected, and black when it’s connected. And, of course, in the ‘receivedDataString’ we get our temperature value, so we simply update the label with the new value.

This is how the view looks when everything is hooked up:

If the Bluetooth is disconnected, the label turns red, like so:

Conclusion

In this article, we saw how to hook up an Arduino with your app. We have a temperature sensor on our Arduino board, but we can easily connect any other sensor to it. With the IoT now becoming a thing, you can hook up a bunch of sensors, and automate your home, for example. Maybe in one of the future posts, we’ll see how to establish a two-way communication between Arduino and iOS, so you can control physical devices with your phone. This was a fun little project that you can actually use every day to measure the room temperature (I’ll keep mine in the living room 🙂 ).

You can find all the code (iOS project and Arduino sketch) on my GitHub account. I hope you’ll find this project useful and fun.

Have a nice day 🙂

Dejan.

More resources

33 thoughts on “Creating a Temperature Sensor for iOS Using BLE and Arduino

  1. mamaro7

    how would you add other characteristics ? like if we wanted to add more sensors that use the i2c serial ports on the arduino how do we differentiate between each sensors data while sending to the app. and how would you make more views to display each sensors data on the app.. sorry if this is too many questions I’m just trying to make a app for my plants health.

    Reply
    1. Dejan Agostini Post author

      If you’re using the same Adafruit BLE breakout circuit as me then you can’t add new characteristics. You only get the TX and RX characteristics. So, if you want to pass data from multiple sensors you would have to send them all using the same characteristic. You can separate the data with a delimiter, for example, if you wanted to send a temperature and a speed, you could format your string like this “23.3|55.3” and in the app you would tokenise the string into an array of doubles, you know the first value is the temperature, and the second is the speed.

      About making more views. I assume you want to create another view controller (let’s say, to display the speed information from the example above), there are many ways of doing this. Take a look at my other post about passing data to your Apple Watch (https://agostini.tech/2017/02/27/sending-data-from-your-app-to-apple-watch-using-wcsession/), and let me know if it helps to make things a bit clearer for you.

      Reply
  2. Emerson Garland

    Fantastic writeup! I’ve been spending the weekend attempting to merge what you’ve presented here using a redbear duo. Running into issues getting it connected. Do you have any experience using the redbear duo?

    Reply
  3. David Kingsland

    I’m looking to build a version of this over the Christmas break. I’ve a little experience with Arduino but none with creating IOS apps. Can you point me to step by step instructions for “iOS Project
    Once you create a new project…” I installed Xcode on my MacBook but it’s a little overwhelming. Many thanks.

    Reply
    1. Dejan Agostini Post author

      Hi David,

      you could use the example project I used in this article as a jumping off point –
      https://github.com/dagostini/DABLETemperatureSensor

      I would recommend that you read one of the ‘getting started’ books I described here: https://agostini.tech/2017/12/11/reading-list-top-10-ios-developer-books/

      If you’re planning on doing something similar to this app, feel free to use the project I put on GitHub, it’s open source 🙂

      I hope these few pointers were useful, and welcome to iOS development, hope you’ll like it 🙂

      Have a nice day 🙂
      Dejan.

      Reply
        1. Dejan Agostini Post author

          You’re welcome 🙂 The code should be a great place to start. You have basic UI, and all of the BLE hooked up. Let me know if I can help you with anything. I’m off on holidays for 3 weeks, so I might be a bit late with my replies.

          Reply
  4. David Picco

    Thank you very much for such a helpful tutorial.

    Up until today I had zero experience with Arduino devices and I have only been programming for iOS for a few weeks.

    Your tutorial was so good I was able to setup all the hardware, upload firmware, build the iPhone app and get it all working perfectly in just a couple hours. If really helped jump start the project I’m working on!

    Reply
  5. Maddie Schaefer

    I am trying to figure out xcode as a first time user. Wondering exactly where each block of this code is placed within the application, and if in those spots, I am supposed to delete the existing code that was there initially before I place in the new code. Any help is appreciated!

    Reply
  6. Maddie Schaefer

    It seems as if this link takes me back to this same website I am viewing. I understand that we have the block of code that is inserted into the arduino IDE, the view controller code into the interface, but am confused on where to place the BLE manager core code, peripheral delegate, and central manager code blocks. I apologize if this is covered somewhere in your link, but I couldn’t find anything new to help me get past this stage. Thanks in advance!

    Reply
    1. Dejan Agostini Post author

      BLEManager is a class on its own, You can just create a new file in your Xcode project and paste the code. Here’s the full listing:
      https://github.com/dagostini/DABLETemperatureSensor/blob/master/DABLETemperatureSensor/BLEManager.swift

      Central and peripheral delegates can go in the same file as the BLEManager..

      Then in your ViewController you will have something like this:
      https://github.com/dagostini/DABLETemperatureSensor/blob/master/DABLETemperatureSensor/ViewController.swift

      You should be able to copy/paste the BLEManager into your project and use it out of the box. It’s hard-coded to connect to the AdafruitBLE breakout board.

      Is it a bit clearer now?

      Reply
      1. Maddie Schaefer

        Yep, thanks for the help. My simulator is showing as a black screen, and not showing what is on the main storyboard. Tried some troubleshooting ideas online, but nothing worked. Did you come across this problem while you were developing this? Any ideas that worked?

        Reply
        1. Dejan Agostini Post author

          I didn’t really come across a problem like that. Did you set your initial view controller, and are you sure that the storyboard is set in your project file (‘Main Interface’ option in the general project settings). Quick question… Is this your project that’s not working, or did you check out my code from GitHub and it’s not working for you?

          Also, you’ll need a device to work with BLE device, simulator won’t work.

          Reply
  7. Maddie Schaefer

    Fixed it! The simulator is showing correctly now. Just trying to connect the temp sensor values to be displayed on the screen.

    Reply
  8. Maddie Schaefer

    The UI View colors were backward, so I was trying to read black text on a black screen.

    To read values, do I connect a label to a piece of code, or how does that all fit together? I want to read the live sensor data in the simulator.

    Reply
    1. Dejan Agostini Post author

      I see, did you change the view color? The background color in the example project should be white.

      If you’re connecting to an arduino box, you will have to use a real device. Your simulator can’t connect to arduino. If you’re using the same arduino setup that I had in this article, then it should work out of the box.

      Reply
    1. Dejan Agostini Post author

      The BLEManager class is hard wired to connect to the Adafruit BLE board that I used for this example. If you have the exact board it should connect automagically 🙂

      Reply
  9. Maddie Schaefer

    I do have have the same board, but the values are not appearing. A couple things I can think of:
    1) The BLE board is a dud (do you get any sign that it is connected? There is a blue light that flickers when I upload, but turns off after a few seconds. Is this a problem? Should it always be blue?)
    2) I need to add something to the xcode or arduino (but I copied and pasted exactly your project)

    Any suggestion is appreciated! Thanks for all your continued help!

    Reply
  10. Dejan Agostini Post author

    1. You can check if your board is transmitting by using an BLE scanner app that will display all the BLE devices nearby. You can use a free app called ‘LightBlue’ on your iPhone. Honestly I don’t remember if there’s a light on the breakout board.
    2. The only reasons that come to my mind are that you miss-connected one of the wires. Check the wiring diagram. Make sure that the temperature sensor is connected properly – maybe you crossed the + and – connections, try flipping them.

    No worries, have a nice day 🙂

    Reply
  11. Maddie Schaefer

    Thanks! Wiring is all correct and I did detect a peripheral on the LightBlue app.

    So basically, I have everything set up, app showing up on my phone, just without the sensor values. Code is all the same as yours. One question: there seems like there should be 2 labels on your viewController (the one displaying the text “temperature” and the other displaying the changing values). All the see is 1 temperatureLabel in your code (which I think is what is supposed to be changing?) I don’t know if this is my problem, do I not have a correct outlet?

    Otherwise, I assume that between the arduino side and iOS side, the values are not sending and receiving properly. Is there a way to tell whether they are or not?

    With a deadline approaching, I am willing to try anything– I am so close! Thinking about trying out a connection with an HC-06, but assuming the bleManager block of code would be different with a different board? Let me know!

    Reply
    1. Dejan Agostini Post author

      The other label is static text, so there’s no need to change it.

      Check the console output if you’re connecting to the Arduino. You can print some logs in the ‘CBCentralManagerDelegate’ callbacks and see what you’re getting into the app. At the top of the BLEManager you’ll see some constants, check with LightBlue app that the UUIDs are the same on the Adafruit BLE chip.

      I’m running out of ideas, tbh.

      You’re right, HC-06 is completely different and it wouldn’t work with this version of BLEManager.

      What kind of a deadline?

      Reply
  12. PH

    Hi,

    The information is really useful!

    Do you have the code that is compatible to Xcode Version 10.2?

    Thank you!

    Reply
  13. arthurpattee

    Hi ! I’m trying to adapt your code to connect to other Arduino devices, but since I’m new to Xcode I’m struggling a bit to understand where I can change this part, could you help me out ?
    Also I’ve updated your existing code to swift 4 if you’re interested 🙂
    Cheers for the howto btw opens up possibilities to me !

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.