Thursday, August 7, 2014

Working with iBeacon's in Swift

Disclaimer: iBeacon is a Trademark owned by Apple.
I am not affiliated with any company mentioned in this tutorial.

I'm still learning Swift so what I'm about to show may not be the best way to do this but it's a start.

Why iBeacons?
Glad you asked, iBeacon technology can be used for proximity checks e.g. Alert your customer when they are in range of your beacon to special sales, let them know their order is ready, tailor a discount to your customer if they happen to be walking down an aisle and pass by an item that is associated with your iBeacon. Monitor heart rates with the wearable iBeacons. Monitor Entry and Exits etc... There are numerous reasons for this technology.  And well it's pretty darn cool!

I'm going to make some assumptions here:

  1. You know your way around Xcode
  2. You are using the latest Beta version of Xcode
  3. You have at least one Beacon hardware that conforms to Apple iBeacon specifications or you have an iOS device you can use as an iBeacon - you can find out if your device supports iBeacon  here https://developer.apple.com/ibeacon/Getting-Started-with-iBeacon.pdf
  4. You have a valid Apple Developer account.


For this tutorial I'm using a TiSensorTag from Texas Instruments




but there are several other manufacturers out there, Estimote, Footworks, BlueCat, Gimbal and many others. Each have their own strengths and weaknesses but I'm not going to talk about them here, I'll leave that up to you. This is just a basic tutorial about interacting with one these devices using Apple's new programming language Swift.

* Before we get started, for the majority of this tutorial iOS 7.1 will work just fine but for Region Monitoring,  your iOS device must be running at least iOS 8.0 which is still in beta so I will go over Region Monitoring at a later date. Hopefully this tutorial will at least get you started!

Create a new Single View Application
I named mine CoffeeTime_iBeacon.
Language: Swift
Devices: Universal
Make sure the Use Core Data box is checked - I like to have Xcode implement the necessary code for me for this just in case I decide to use it later on,  even though we won't talk about Core Data in this tutorial I plan to follow up on this in later tutorials using this same application.

Under Deployment Info:
Make sure the Deployment Target is set to at least 7.1

Frameworks:

  • CoreFoundation
  • CoreLocation
  • CoreBluetooth
  • CoreData
  • MapKit
  • UIKit
  • QuartzCore
  • Social - (Optional)
I added the framework Social just for the fun of it, I may want to send some test results to Facebook or Twitter etc... , why because I think it might be fun and almost everything is Social these days so who am I to go against the trend :)

Layout the UI:

Open Main.storyboard even though I don't directly use the button in this tutorial, at least it's there if you want to scan for beacons manually. Drag a button onto the view controller, replace the default text with something like Scan For Beacons 

Now let's create an Outlet for the button, control drag from the button to the top of viewController.swift above the viewDidLoad function. Name this anything you like, I chose you guessed it "scanForBeacons". Control drag one more time but this time create an action, I chose to go with the same name "scanForBeacons"

Drag a Text View and place it just below the button. I deleted all the placeholder text, and expanded it to both edges, gave it a height of 472 and changed it's background to a light blue, again that's just me.

Once again Control drag from the text view to the viewController.swift just below the outlet for scanForBeacons, I created an outlet called "sensorData"

Onto the Code:

Open ViewController.swift, before we code anything there are several terms you need to be aware of:

iBeacon - this is a term owned by Apple. The actual devices are called beacons, the term iBeacon means that the device conforms to Apple's iBeacon standards. To use the term iBeacon you have to have a license from Apple. I will refer to the devices as Peripheral's going forward.

Peripheral -  this is the Beacon itself
Central - this is our main program 

Now we need to setup our ViewController class as a delegate to not only the peripheral and central but also the Location Manager.

You could and probably should put these into their own files but for this tutorial, I'm going to keep everything in the ViewController class.

First import CoreLocation, CoreBluetooth and Foundation and change our class declaration to 


class ViewController: UIViewController,CLLocationManagerDelegate,CBCentralManagerDelegate,CBPeripheralManagerDelegate,CBPeripheralDelegate {

You will get two errors that Type "ViewController" does not conform to protocol 'CBPeripheralManagerDelegate' and 'CBCentralManagerDelegate', that's because we have not implemented the protocol's DidUpdateState required methods just yet but we will fix this shortly.



Let's create our Managers:


    var cManager = CBCentralManager()
    var peripheralManager = CBPeripheralManager()
    var locManager = CLLocationManager()
    
Create our beaconRegion and discoveredPeripheral vars, this will be explained later.
   var beaconRegion = CLBeaconRegion()
   var discoveredPeripheral:CBPeripheral?



in viewDidLoad(), below super.viewDidLoad

add:
// Init the CentralManager, we set the queue to nil so that the manager dispatches the central role events on the main queue - refer to the CBCentralManager Class definition for further explanation
        cManager = CBCentralManager(delegate: self, queue: nil)

// Init the PeripheralManager
        peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
        
// Init the RegionManager
        locManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locManager.delegate = self

Please Note: There are several other options for the kCLLocationAccuracy. 

        kCLLocationAccuracyBest
        kCLLocationAccuracyBestForNavigation
        kCLLocationAccuracyHundredMeters
        kCLLocationAccuracyThreeKilometers
        kCLLocationCoordinate2DInvalid

But for this post I want it to detect the beacon at the closest range (kCLLocationAccuracyNearestTenMeters)

Ok time to implement our first required delegate method this will be for our CentralManager delegate.

Required Method #1.
We need to detect that our Central device can support Bluetooth Low Energy and that it is available.
You cannot issue commands to the beacon until after the .PowerOn state is achieved, so once the Central Manager is in the poweredOn state we can call our scanForBeacons, we will implement our scanForBeacons method shortly. 

func centralManagerDidUpdateState(central: CBCentralManager!) {
        
        switch cManager.state {
            
        case .PoweredOff:
            println("CoreBluetooth BLE hardware is powered off")
            self.sensorData.text = "CoreBluetooth BLE hardware is powered off\n"
            break
        case .PoweredOn:
            println("CoreBluetooth BLE hardware is powered on and ready")
            self.sensorData.text = "CoreBluetooth BLE hardware is powered on and ready\n"
            // We can now call scanForBeacons
            self.scanForBeacons(self)
            break
        case .Resetting:
            println("CoreBluetooth BLE hardware is resetting")
            self.sensorData.text = "CoreBluetooth BLE hardware is resetting\n"
            break
        case .Unauthorized:
            println("CoreBluetooth BLE state is unauthorized")
            self.sensorData.text = "CoreBluetooth BLE state is unauthorized\n"

            break
        case .Unknown:
            println("CoreBluetooth BLE state is unknown")
            self.sensorData.text = "CoreBluetooth BLE state is unknown\n"
            break
        case .Unsupported:
            println("CoreBluetooth BLE hardware is unsupported on this platform")
            self.sensorData.text = "CoreBluetooth BLE hardware is unsupported on this platform\n"
            break
            
        default:
            break
        }
    }

Now implement the scanForBeacons method

@IBAction func scanForBeacons(sender: AnyObject) {
        cManager.scanForPeripheralsWithServices(nil, options: nil)
        sensorData.text = "\nNow Scanning for PERIPHERALS!\n"
    }


Now that our Central device is in the poweredOn state and we've called scanForBeacons to kickoff the Peripheral discovery routine we need to know when the Peripheral was discovered.

Create an optional var at the top the class and implement the 

var discoveredPeripheral:CBPeripheral?

We need to implement the centralManager didDiscoverPeripheral method.

func centralManager(central: CBCentralManager!,
        didDiscoverPeripheral peripheral: CBPeripheral!,
        advertisementData: [NSObject : AnyObject]!,
        RSSI: NSNumber!) {
            
            central.connectPeripheral(peripheral, options: nil)
            
             // We have to set the discoveredPeripheral var we declared earlier to reference the peripheral, otherwise we won't be able to interact with it in didConnectPeripheral. And you will get state = connecting> is being dealloc'ed while pending connection error.

            self.discoveredPeripheral = peripheral
            
            var curDevice = UIDevice.currentDevice()
            
            //iPad or iPhone
            println("VENDOR ID: \(curDevice.identifierForVendor) BATTERY LEVEL: \(curDevice.batteryLevel)\n\n")
            println("DEVICE DESCRIPTION: \(curDevice.description) MODEL: \(curDevice.model)\n\n")
            
            // Hardware beacon
            println("PERIPHERAL NAME: \(peripheral.name)\n AdvertisementData: \(advertisementData)\n RSSI: \(RSSI)\n")
            
            println("UUID DESCRIPTION: \(peripheral.identifier.UUIDString)\n")
            
            println("IDENTIFIER: \(peripheral.identifier)\n")
            
            sensorData.text = sensorData.text + "FOUND PERIPHERALS: \(peripheral) AdvertisementData: \(advertisementData) RSSI: \(RSSI)\n"
            
            // stop scanning, saves the battery
           cManager.stopScan()
            
    }

This delegate method is called when the CentralManager establishes a successful connection to the peripheral

func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) {
        
        peripheral.delegate = self
        peripheral.discoverServices(nil)
        
        println("Connected to peripheral")
        
    
}

This delegate method is called when the CentralManager Fails to establish a connection to the peripheral

func centralManager(central: CBCentralManager!, didFailToConnectPeripheral peripheral: CBPeripheral!, error: NSError!) {
        sensorData.text = "FAILED TO CONNECT \(error)"
    }

Required Method #2.
Ok were almost done, we still have one error to correct Type "ViewController" does not conform to protocol 'CBPeripheralManagerDelegate'. To fix this we implement the peripheralManagerDidUpdateState method.

Just like the centralManager we cannot interact with the peripheral until it is in the poweredOn state.

func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager!) {
        
        switch peripheralManager.state {
            
        case .PoweredOff:
            sensorData.text = "Peripheral - CoreBluetooth BLE hardware is powered off"
            break
            
        case .PoweredOn:
            sensorData.text = "Peripheral - CoreBluetooth BLE hardware is powered on and ready"
            break
            
        case .Resetting:
            sensorData.text = "Peripheral - CoreBluetooth BLE hardware is resetting"
            break
            
        case .Unauthorized:
            sensorData.text = "Peripheral - CoreBluetooth BLE state is unauthorized"
            break
            
        case .Unknown:
            sensorData.text = "Peripheral - CoreBluetooth BLE state is unknown"
            break
            
        case .Unsupported:
            sensorData.text = "Peripheral - CoreBluetooth BLE hardware is unsupported on this platform"
            break
            
        default:
            break
        }
        
    }

Now we can discover what services the peripheral offers

// Invoked when you discover the peripheral's available services.
    func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) {
        println("ERROR: \(error)")
        
        var svc:CBService
        
        for svc in peripheral.services {
            println("Service \(svc)\n")
            sensorData.text = sensorData.text + "\(svc)\n"
            println("Discovering Characteristics for Service : \(svc)")
            peripheral.discoverCharacteristics(nil, forService: svc as CBService)
        }
    }

And the services characteristics

// Invoked when you discover the characteristics of a specified service.
    func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!)
    {
        sensorData.text = devices.text + "\n\nCHARACTERISTICS\n\n"
        var myCharacteristic = CBCharacteristic()
        
                for myCharacteristic in service.characteristics {
            sensorData.text = devices.text + "\nCharacteristic: \(myCharacteristic)\n"
            
            println("didDiscoverCharacteristicsForService - Service: \(service) Characteristic: \(myCharacteristic)\n\n")
            
           
            peripheral.readValueForCharacteristic(myCharacteristic as CBCharacteristic)
            
        }
    }

Turn on your beacon and click Run
If there are no errors you should detect and connect to the beacon and be presented with quite a bit of data like,

PERIPHERAL NAME: TI BLE Sensor Tag
 AdvertisementData: [kCBAdvDataLocalName: SensorTag, kCBAdvDataTxPowerLevel: 0, kCBAdvDataChannel: 37, kCBAdvDataIsConnectable: 1]
 RSSI: -46

UUID DESCRIPTION:

IDENTIFIER: 

Connected to peripheral
ERROR: nil
Service <CBService: 0x176a5dd0 Peripheral = <CBPeripheral: 0x17560840 identifier = , Name = "TI BLE Sensor Tag", state = connected>, Primary = YES, UUID = Device Information>

Discovering Characteristics for Service : <CBService: 0x176a5dd0 Peripheral = <CBPeripheral: 0x17560840 identifier = , Name = "TI BLE Sensor Tag", state = connected>, Primary = YES, UUID = Device Information>
Service <CBService: 0x1769d430 Peripheral = <CBPeripheral: 0x17560840 identifier = , Name = "TI BLE Sensor Tag", state = connected>, Primary = YES, UUID = >

didDiscoverCharacteristicsForService - Service: <CBService: 0x176a5dd0 Peripheral = <CBPeripheral: 0x17560840 identifier = , Name = "TI BLE Sensor Tag", state = connected>, Primary = YES, UUID = Device Information> Characteristic: <CBCharacteristic: 0x1769ba70 UUID = Model Number String, Value = (null), Properties = 0x2, Notifying = NO, Broadcasting = NO>

Please note: I removed the UUID and identifier information from my examples for security purposes.

Let's also monitor if the characteristics have been updated, more on this later.

// Invoked if the characteristic is updated
    func peripheral(peripheral: CBPeripheral!, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {
        
        println("\nCharacteristic \(characteristic.description) isNotifying: \(characteristic.isNotifying)\n")
        
        if characteristic.isNotifying == true {
            
            peripheral.readValueForCharacteristic(characteristic as CBCharacteristic)
        }
        
    }

Hopefully you found this helpful.

In my Next tutorial I'll go over setting up Region Monitoring: (this requires iOS 8.0 or greater)
and a few other things we can do with this.








.NetCore 2.0, AzureAD and OpenIdConnect

.NetCore 2.0, AzureAD and OpenIdConnect If you develop with .NetCore you probably already know that there are some significant changes fr...