element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Pi IoT
  • Challenges & Projects
  • Design Challenges
  • Pi IoT
  • More
  • Cancel
Pi IoT
Blog [Pi IoT] Thuis #13: Presence monitoring using iBeacons
  • Blog
  • Forum
  • Documents
  • Polls
  • Files
  • Events
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: rhe123
  • Date Created: 25 Aug 2016 11:04 PM Date Created
  • Views 1176 views
  • Likes 2 likes
  • Comments 4 comments
  • beacon
  • ibeacon
  • piiot
  • eddystone
  • ios
  • swift
  • presence
  • thuis
  • estimote
Related
Recommended

[Pi IoT] Thuis #13: Presence monitoring using iBeacons

rhe123
rhe123
25 Aug 2016

One of the goals of Thuis is optimizing it's own rule engine based on actual activity in the house. Although time is to short to actually start the optimization, with this blog we'll start collecting presence data using iBeacons. As a bonus we solve the Welcome Home use case!

 

iBeacons monitoring, ranging and indoor location

iBeacons (in my case by Estimote) are little Bluetooth LE devices which on regular intervals broadcast their identifiers. Mobile apps can use this as a way of determining their location. They can be used in several ways. The three most common ways are:

  • Monitoring – the app gets a signal whenever it enters the region defined by one or multiple beacons (aka when it receives a packet sent by the them). The app gets a small amount of time to do some work, for example notify the user. This works even when the app is terminated.
  • Ranging – When the app is active it can listen to all beacons around and based on the signal strengths make an approximation of the distance between the beacon and the phone.
  • Indoor location – Estimote developed another layer on top of ranging. Based on several beacons (at least one per wall) and the sensors in the phone it can determine its location within a space. This works quite precise, but it uses more energy than the other methods. Also it can only be used when the app is in use.

 

I experimented with all three to see which way is most usable for Thuis. Indoor location was a bit of a hassle to set up (it involves walking lots of circles close to the walls through the house, which is not easy when there is furniture!), but the result is impressive. Not being able to use it in the background made me choose monitoring as the main technique.

 

ps: for the Android users out here: although I'm talking about iBeacons (by Apple) there are very similar technologies available for other platforms. For Android that's Eddystone, which is the Estimote beacons can broadcast as well.

 

Presence monitoring

To optimize the Thuis rule engine it needs to have knowledge of what happens around. One of the useful things to now is who is where and when. We'll use presence monitoring to determine who is currently where in the house.

 

Hardware set up

We want to know who is where on a room-level, so who is in which room. To identify different rooms we'll deploy a iBeacon is every room we're interested in. We're using 3 Estimote Location Beacons and 3 older Estimote Proximity Beacons. In the entrance, living room, bedroom, kitchen and office 1 beacon is installed at the center of the outer wall (as far as possible from other beacons). The signal strength of each of them is tweaked ranging from -30dBm (±1.5 meter) in the smallest rooms to -20dBm (±3.5m) in the bigger rooms. Each beacon is configured to broadcast its ID approximately 3 times per second. These values will likely be optimized further over the coming period.image

 

Initial version

I started by using just region monitoring as this is very easy to implement. To make my life easier I started with some structs describing the deployed beacons together with some helper functions. The interface of the struct looks like:

struct BeaconID : Equatable, CustomStringConvertible, Hashable {
    let proximityUUID: UUID
    let major: CLBeaconMajorValue?
    let minor: CLBeaconMinorValue?
    let identifier: String?

    init(proximityUUID: UUID, major: CLBeaconMajorValue?, minor: CLBeaconMinorValue?, identifier: String?)
    // more init methods

    var asBeaconRegion: CLBeaconRegion { get }
}

internal func ==(lhs: BeaconID, rhs: BeaconID) -> Bool

extension CLBeacon {
    var beaconID: BeaconID? { get }
}

 

And the beacons are defined as:

struct BeaconIDs {
    private static let uuidString = "B9407F30-F5F8-466E-AFF9-25556B57FE6D"
    
    static let all = [home, bedroom, office, living, kitchen, entrance]
    
    static let home = BeaconID(uuidString: uuidString, identifier: "home")

    static let bedroom = BeaconID(uuidString: uuidString, major: 23476, minor: 64333, identifier: "bedroom")
    static let office = BeaconID(uuidString: uuidString, major: 25568, minor: 21134, identifier: "office")
    static let living = BeaconID(uuidString: uuidString, major: 40474, minor: 19278, identifier: "living")
    static let kitchen = BeaconID(uuidString: uuidString, major: 16433, minor: 64211, identifier: "kitchen")
    static let entrance = BeaconID(uuidString: uuidString, major: 16433, minor: 21894, identifier: "entrance")
    
    static func of(identifier: String) -> BeaconID? {
        return all.first(where: { (beaconId) -> Bool in
            return beaconId.identifier == identifier
        })
    }

    static func of(proximityUUID: UUID, major: CLBeaconMajorValue, minor: CLBeaconMinorValue) -> BeaconID? {
        return all.first(where: { (beaconId) -> Bool in
            return beaconId == BeaconID(proximityUUID: proximityUUID, major: major, minor: minor)
        })
    }
}

 

All code related to beacons takes place in the class BeaconManager. It sets up monitoring in the init method and implements the delegate methods for the ESTBeaconManagerDelegate. It keeps some information about which is the current region for this phone and for each beacon when was the last time this phone entered or left the region. The initial version just logs information to the console based on activity.

class BeaconManager: NSObject, ESTBeaconManagerDelegate {
    private let beaconManager = ESTBeaconManager()
    
    private var currentPresence: BeaconID?
    private var lastLeftOrEntered: [BeaconID: Date] = [:]
    
    override init() {
        super.init()
        
        beaconManager.delegate = self
        beaconManager.requestAlwaysAuthorization()
        
        for beaconID in BeaconIDs.all {
            beaconManager.startMonitoring(for: beaconID.asBeaconRegion)
        }
    }
    
    func beaconManager(_ manager: Any, didDetermineState state: CLRegionState, for region: CLBeaconRegion) {
        guard let beaconID = BeaconIDs.of(identifier: region.identifier) else {
            return
        }
        
        print("State \(beaconID.identifier!): \(state.rawValue)")
    }
    
    func beaconManager(_ manager: Any, didEnter region: CLBeaconRegion) {
        guard let beaconID = BeaconIDs.of(identifier: region.identifier) else {
            return
        }

        currentPresence = beaconID
        lastLeftOrEntered[beaconID] = Date()
        
        if let timeIntervalSinceNow = lastLeftOrEntered[beaconID]?.timeIntervalSinceNow {
            print("Entered \(beaconID.identifier!) since \(timeIntervalSinceNow)")
        }
    }
    
    func beaconManager(_ manager: Any, didExitRegion region: CLBeaconRegion) {
        guard let beaconID = BeaconIDs.of(identifier: region.identifier) else {
            return
        }

        print("Left \(beaconID.identifier!)")
        
        lastLeftOrEntered[beaconID] = Date()

        if beaconID.identifier == BeaconIDs.home.identifier {
            currentPresence = nil
        }
    }
}

 

This already works quite well, but as we're working with wireless signals there can be mistakes. For example a packet from another room reaches your phone and therefor your presence is adjusted. Or some packets get lost and cause your phone to think it left the region.

 

The latter is the reason that for resetting the currentPresence to outside we use the home region. This region consists all beacons, so the change of a false positive is smaller. It does however still happen every now and then.

 

Improving accuracy with ranging

To make presence monitoring more accurate we can combine monitoring with ranging. When the app is in the background and enters a region it's woken up and gets a small amount of time to do some work. We can use this time to start ranging beacons, and with the more detailed data about the beacons around us make a better approximation.

 

To start ranging we have to adjust the didEnter method, instead of updating currentPresence and lastLeftOrEntered we start ranging: beaconManager.startRangingBeacons(in: BeaconIDs.home.asBeaconRegion). To receive the results we'll implement the corresponding delegate method in which we'll update the values:

func beaconManager(_ manager: Any, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
    guard let beaconID = beacons.first(where: {$0.proximity != .unknown})?.beaconID else {
        return
    }

    currentPresence = beaconID
    lastLeftOrEntered[beaconID] = Date()
    
    beaconManager.stopRangingBeacons(in: BeaconIDs.home.asBeaconRegion)
}

 

Notice we'll take the first beacon (with a known proximity) from the given list. The SDK returns them ordered from close to far away, so we'll always use the closest beacon. Our value of currentPresence got a lot more accurate now!

 

Based on how it performs I'll do some further optimizations. One thing I still have to do is make the values of currentPresence and lastLeftOrEntered persistent, so they will survive a termination of the app.

 

Publishing presence

As always we want to publish our data through MQTT, so the other Thuis nodes can use it as well. In [Pi IoT] Thuis #10: MQTT User Interface components for iOS we added MQTT to the app, so we can build on this. Whenever the currentPresence value changes we'll have to publish a message. This means we have to update both the didRangeBeacons method and the didExitRegion method.

 

didRangeBeacons is updated like this:

func beaconManager(_ manager: Any, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
     // ...
     if currentPresence != beaconID {
         MQTT.sharedInstance.publish(beaconID.identifier!, topic: "Thuis/presence/robin", retain: true)
         currentPresence = beaconID
     }
     // ...
}

 

And to detect someone leaving the house we change didExitRegion, here we only publish when the specific region is home:

func beaconManager(_ manager: Any, didExitRegion region: CLBeaconRegion) {
    // ...
    if beaconID.identifier == BeaconIDs.home.identifier {
        MQTT.sharedInstance.publish("outside", topic: "Thuis/presence/robin", retain: true)
        currentPresence = nil
    }
}

 

Note that the name in the topic is still static, I'll make it configurable in the app later.

 

Walking around slowly through the house gives the following MQTT messages:

$ mosquitto_sub -t Thuis/presence/# -v 
Thuis/presence/robin living
Thuis/presence/robin entrance
Thuis/presence/robin bedroom
Thuis/presence/robin entrance
Thuis/presence/robin kitchen
Thuis/presence/robin office

 

Welcome home

Based on the same events we can welcome a user home as well and directly give him a useful action. I could directly turn on some lights for example, but I rather give the user the choice. So we'll send the user a notification with an action. We'll start with a single action, but later multiple actions can be added depending on for example the time or person.

 

When we get home we often watch an episode of a TV series (currently we're watching The Mentalist, very nice show!), so the action of choice will be turning on the home theatre system.

 

Sending a notification is easy. In the BeaconManager we create a function for it:

func sendLocalNotification() {
    let notification: UILocalNotification = UILocalNotification()
    notification.alertAction = "Watch TV"
    notification.alertBody = "Welcome home!"
    notification.soundName = UILocalNotificationDefaultSoundName
    
    UIApplication.shared.presentLocalNotificationNow(notification)
}

 

We'll call it from the didEnterRegion method when we enter the home region. To avoid getting too many notifications in case of exiting and entering the region by accident we'll add a cool down period of 5 minutes. This looks as follows:

if beaconID.identifier == BeaconIDs.home.identifier 
   && (lastLeftOrEntered[beaconID] == nil || (lastLeftOrEntered[beaconID]?.timeIntervalSinceNow)! < -60*5) {
    sendLocalNotification()
}

 

The result is you receive this notification when you arrive home:

image

 

To make it work there is one more thing to do and that's implementing another delegate method, this time in the AppDelegate:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    // ...

    func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
        // If the app is already active, don't automatically start TV
        if application.applicationState == .active {
            return;
        }
        
        MQTT.sharedInstance.publish("on", topic: "Thuis/scene/homeTheater", retain: false)
    }
}

 

So when the notification action is used it will publish a MQTT message which enables the Home Theater scene and you can directly start watching your favorite series. How the home theater scene works will be the subject of the next blog!

  • Sign in to reply

Top Comments

  • mg.sergio
    mg.sergio over 9 years ago +2
    Very nice update Robin! I like your approach of using the iBeacons for presence monitoring and welcome home. I like the fact you can monitor who is where, at a room level. Whilst with my approach if there…
  • rhe123
    rhe123 over 9 years ago in reply to mg.sergio +1
    Thanks mg.sergio ! It's indeed quite versatile. Just noticed that yesterday Estimote released a new version of their SDK. They added a very interesting feature: Estimote Monitoring support. It allows apps…
  • DAB
    DAB over 9 years ago +1
    Great update. You have done an excellent job of making this task look easy to do. DAB
  • volly
    volly over 9 years ago

    rhe123, pretty neat.! Salute!

    YV

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB over 9 years ago

    Great update.

     

    You have done an excellent job of making this task look easy to do.

     

    DAB

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • rhe123
    rhe123 over 9 years ago in reply to mg.sergio

    Thanks mg.sergio! It's indeed quite versatile. Just noticed that yesterday Estimote released a new version of their SDK. They added a very interesting feature:

    Estimote Monitoring support. It allows apps to detect when somebody is entering and exiting the range of beacons. Compared to iOS Core Location Monitoring, it allows for more granular enters/exits without any region limit or exit timeout.

    Looks like I have another possible way of improving the accuracy now!

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • mg.sergio
    mg.sergio over 9 years ago

    Very nice update Robin!

     

    I like your approach of using the iBeacons for presence monitoring and welcome home. I like the fact you can monitor who is where, at a room level. Whilst with my approach if there are two or more people at home, I can´t determine who is where.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube