In 2013 Apple Inc. introduced a technology called iBeacon that runs on top of Bluetooth 4.0 protocol which is also known as Bluetooth Low Energy or BLE. While Bluetooth enabled devices can identify themselves, the process is generally 1 to 1 with a host pairing with a device, such as a smart phone pairing with a Bluetooth headset. The iBeacon is simply a broadcast service that sends out a few pre-configured pieces of information and can run in parallel with other Bluetooth services. For discussion, the four parameters of interest when creating beacon applications from solutions like Estimote or from Gimbal are the UUID String, a major 16-bit number, a minor 16-bit number, and a signal strength value.
On iOS7, Apple Inc. provides library functionalities for working with iBeacons and while there are 3rd party libraries for other platforms, the blog here will be focused on iOS. Combining Couchbase Mobile technologies, Ed Arenberg and the EPage team is developing a service that makes heavy use of iBeacon and utilizes Couchbase Lite for local storage while synchronizing data among numberous devices by using Couchbase Sync Gateway.
Let us now explore the 3 key components of this iBeacon service and how to implement these core features using Couchbase Mobile technologies. Note all of the code is written in Swift 1.2.
Saving data locally on the device to Couchbase Lite
The service will first collect information from nearby beacons and log device informations to the database. For your exploration, Apple has a good amount of documentation on how to locate and range nearby iBeacons. For integrating Couchbase Lite into your mobile app, you may look at the Couchbase Mobile developer portal for reference sample code.
ORM Implementation
We will have the app keep objects in native classes and will implement an ORM to map to and from the Couchbase representation. When the app locates a group of iBeacons, their class objects will be generated. The class includes code for saving the object to Couchbase and for loading it from Couchbase. Here is the class for holding an iBeacon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class Beacon : EntityDocument { var proximityID = “XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" var major : Int? = 0 var minor : Int? = 0 override func save() { let dict = NSMutableDictionary() dict["type"] = "gbeacon" dict["proximityID"] = proximityID dict["major"] = major ?? 0 dict["minor"] = minor ?? 0 super.save(dict) } override func load(document: CBLDocument) { super.load(document) proximityID = document.properties["proximityID"] as! String major = document.properties["major"] as? Int minor = document.properties["minor"] as? Int } } //EntityDocument is a base class for various objects: class EntityDocument { var document : CBLDocument? init () {} func save(dict: NSDictionary) { if document == nil { document = Database.sharedDatabase.createDocument(dict, object:self) } else { var error: NSError? var properties = NSMutableDictionary(dictionary: document!.properties) properties.addEntriesFromDictionary(dict as [NSObject : AnyObject]) if document!.putProperties(properties as [NSObject : AnyObject], error: &error) == nil { NSLog("%@", error!) } } } func load(document: CBLDocument) { self.document = document } func delete() { var error : NSError? if let doc = document { if !doc.deleteDocument(&error) { // Handle Error } } } } //Now can create Beacon object from a discovered 'foundBeacon'(CLBeacon iOS object) and save it simply with: let beacon = Beacon() beacon.proximityID = foundBeacon.proximityUUID beacon.major = foundBeacon.major beacon.minor = foundBeacon.minor beacon.save() } |
Syncing Beacon Data
When you are connected to a network, Couchbase Mobile will sync out data using Couchbase Sync Gateway. This is handled automatically once we set up our push and pull replications, which makes it very easy to keep the system coordinated. We can add an observer method to listen for the state of the push and pull operations. However, each device needs to listen for changes in the database for which it needs to take action, so we can add an observer for a database change notification. When the database changes, we update our local native objects. To manage the Couchbase interaction, create a Database object that instantiates a singleton. The main concepts are delineated in the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
private let _DatabaseSharedInstance = Database() class Database { var manager: CBLManager = CBLManager.sharedInstance() var db: CBLDatabase var my_push: CBLReplication var my_pull: CBLReplication class var sharedDatabase: Database { return _DatabaseSharedInstance } init() { var replication_url = NSURL(string: REPLICATION_URL) var error :NSError? db = manager.databaseNamed(DATABASE_NAME, error: &error) let push = db.createPushReplication(replication_url) let pull = db.createPullReplication(replication_url) push?.continuous = true pull?.continuous = true my_push = push my_pull = pull push.start() pull.start() NSNotificationCenter.defaultCenter().addObserver(self, selector: "replicationChanged:", name: kCBLReplicationChangeNotification, object: push) NSNotificationCenter.defaultCenter().addObserver(self, selector: "replicationChanged:", name: kCBLReplicationChangeNotification, object: pull) NSNotificationCenter.defaultCenter().addObserver(self, selector: "databaseChanged:", name: kCBLDatabaseChangeNotification, object: db) } @objc func databaseChanged(notification: NSNotification) { if let changes = notification.userInfo!["changes"] as? [CBLDatabaseChange] { for change in changes { NSLog("%@ changed", change.documentID) updateObject(change.documentID) } } } @objc func replicationChanged(notification: NSNotification) { let active = my_pull.status == CBLReplicationStatus.Active || my_push.status == CBLReplicationStatus.Active NSLog("%@ in replication changed: %@", notification.object!.description, active) // Now show a progress indicator: if active { var progress = 0.0 let total = my_push.changesCount + my_pull.changesCount let completed = my_push.completedChangesCount + my_pull.completedChangesCount if total > 0 { progress = Double(completed) / Double(total); NSLog("progress: %f", progress) } } } } |
These are the main pieces that allow the mobile app to run local activities such as monitoring for nearby beacons, saving its local state, and set up sync services to coordinate information consistency amongst app instances. From this you can build up more functionality and create sophisticated services that can synchronize state across a network of apps. In the next blog we will explore the use of beacon and Couchbase Mobile technologies together to deliver location aware apps that work without a network connection.