This blog is part of a series of entries documenting how we are using iBeacons and Liferay to better engage our audiences at events throughout the year. [Part 1] [Part 2] [Part 3]
Update: Check out my new eBook! Finding Your Way Around iBeacons for Event Marketing.
Download the eBook
In Part 1 I introduced the concept of using iBeacons for Audience Engagement and reviewed knowing your audience and the physical space. In Part 2 we took a step down and covered more technical details about beacon programming and management. In this third and final installment we'll discuss how we use Liferay to manage beacon configuration, some anti-annoyance strategies, and a comparison of iOS to Android when it comes to beacons.
With all of your newfound knowledge about how iBeacons work, let's talk about how to integrate them into the Liferay Portal platform.
There are many variables to consider when designing an app that will interact with Bluetooth beacons. How many different spaces and times you want to support, the physical size and layout of the space (your local user group meetup, or the World Cup?), what kinds of interactions you wish to have, how crowded the space will be, how many beacons you can get your hands on, and a whole lot more. This means that your beacon configuration is most likely going to be changing a lot, and in some cases will be unknown until you are actually present to do setup. So no hard-coding of beacon identifiers or region names or anything of the sort is allowed!
In addition, as we previously discussed, beacons are pretty dumb - they simply emit a unique code over and over again (you know, like... a beacon). The interesting stuff happens when your app responds with unique and engaging content.
So, your app needs to be able to reconfigure itself on the fly by reading dynamic beacon configuration from a network location and serve unique and engaging content based on user behavior in the field. Of course we would use Liferay, as it does this really well!
Dynamic Data Lists
When you start up the Liferay Events app, it asynchronously downloads its data and beacon configuration from Liferay in the form of Dynamic Data Lists. This Liferay feature allows you to create lists of... dynamic data. You can try this now: access the real, live DDL data using liferay.com's JSON web services by reading through the "DIY: Liferay Events Hacks" blog!
We needed to massage the data coming out of the DDL a bit (for example, we add a UUID to every returned record to use for internal record keeping in the app, which by default is not present, and we also wanted the date format to be consistent), so we created a very simple plugin on top of the standard DDL service which returns the data from the DDL in a friendlier format using remote JSON web services (if you want to learn how to do this, don't miss tomorrow's dev.life session!).
For Liferay events, each event typically gets a separate Site on liferay.com (we call them 'microsites'). Here's the DevCon microsite. Within each microsite lives the data for the event (as discussed in the DIY blog), including beacon configuration described below.
We designed 4 different dynamic data lists to hold the beacon config for each event to support the kinds of interactions we described in part 2 of this blog. Basically, the 4 lists provide beacon region definitions, event definitions, and forms to fill out for certain kinds of interactions.
1. The Beacon Regions List
This list defines the set of beacon regions for a given event. For example, in the Benelux Solutions Forum we had 5 different regions. As you know, each region is defined with an identifier, and one or more of Beacon UUID, Major, and Minor. So that's the schema for this dynamic data list:
name - A human-readable identifier that shows up in the app (but is otherwise unknown to the beacons)
beacon_uuid - The hardware UUID of all beacons in this region
beacon_major - The hardware major number of all beacons in this region (or blank to represent any major number)
beacon_minor - The hardware minor number of all beacons in this region (or blank to represent any minor number)
muted - a flag that allows event staff to selectively turn off a region for any particular reason
2. The Beacon Region Events List
This list defines the things that can happen based on region actions such as entering or leaving one of the above defined regions. This turned out to be the biggest list compared to the others, because most interactions are based on region entry/exit (which is the only thing your app can detect when it's backgrounded or quit entirely). We had a particularly strong desire for this application to NOT be annoying, so most of the elements below are designed to STOP the app from doing anything! Only those events deemed most worthy make it through the gauntlet of checks represented by the below. So each possible interaction is defined with this schema:
region_name - A textual description of the region for which the action should occur (this name shows up in the app as well)
on - A 'select' field to define the kind of event for this region - entry or exit.
initial_delay - How long to wait after entry/exit before the notification is shown. This allows you to delay the notification (e.g. waiting 10 minutes after entering the Cafe before offering a discounted drink ticket). It also works with the cancelable flag below to avoid transient events and NOT trigger interactions in certain situations.
repeat - How many times to repeat this specific notification. No sense in repeating most messages, right? Maybe 1-5 times, but more than that and people get annoyed.
min_repeat_period - How long to wait before repeats. If you have a repeating message (e.g. a "Welcome to the sponsor expo, sign in to get your prize" kind of interaction, you would get annoyed if you got that every time you walked in!)
cancelable - A flag to indicate that a delayed notification can be cancelled if the user reverses their behavior. See Death Row below.
message - the initial message to display (and we have string substitution, for example
$SPEAKER_NAME so we can show dynamic messages, based on the agenda and time of day)
actions - the options to present. Each notification consists of the message and an optional set of actions the user can take. For example, in a "Welcome to the event" message you might want to offer a quick link to the registration desk map, a link to the agenda, or a link to checkin on Foursquare or Twitter, or a form to fill out to register for something. We invented our own simple list syntax here to encode a list of actions into a String, which identifies different kinds of actions (take the user to a specific screen in the app, load a web page, show a document or form, create a tweet that the user can edit and send, etc).
preferences_gate - We created this to allow logic to be introduced as part of the gamification design. Preferences can be programmatically set based on user behavior (e.g. filling out a form, finding a mystery object, or completing a game quest), and those preferences can then be checked later on using this field to avoid showing the wrong notification, or duplicate notifications for game elements that have already been completed.
start_date/start_time/end_date/end_time - A way to avoid interactions at certain times of the day. For example, you don't want to show a "Come join us after today's sessions for happy hour!" if happy hour took place yesterday.
All of these configurations are designed to filter the interactions to only those that really should show up. In addition, the app also hard-codes a global event frequency limit, so even the most "chatty" event staffers can be thwarted in their plan to fill your notification tray (see below for more detail on anti-annoyance)
3. The Individual Beacon Events List
In addition to region-wide events (entry/exit) we also want some interactions to take place within the context of a specific beacon. The schema is virtually identical to those for region-wide events, but in this case you define the action to occur based on proximity to a specific beacon:
beacon_name - The name of the beacon (a human-readable name, e.g. "Mystery Object 5")
region_name - The name of the region in which the person must be in for this event to occur. Because beacons can be in multiple logical regions at a given time (remember the overlapping regions?), we wanted some actions to only occur relative to a specific region.
beacon_major/beacon_minor - Since this event is specific to a specific beacon, you need to identify it here!
on_proximity - How close you have to get to the beacon before triggering the interaction. It's a multi-select field for immediate, near, or far, or any.
The rest of the schema is identical to the schema for Region Events.
4. Beacon Forms
One of the interactive features of the Liferay Events app is the ability to present forms to the user as part of a region or beacon trigger. We used this as part of a "mystery guest" game where attendees would walk around to each sponsor booth and be presented a form, and if they entered the right values they'd be entered into a drawing for a prize. The form is submitted to a special game server which evaluates the answers and returns results (a simple Liferay web service). So in the above event definitions, there is an "Actions" field and one of those actions is to present one of these forms.
form_id - The identifier for the form (referenced from the actions list above)
title / subtitle / sub-subtitle / intro - These are textual elements at the top of the form (introductory text for describing the purpose of the form)
form - The form itself. The syntax here is out of scope for this blog, but it basically allows you to define the fields of the form itself (text fields, text areas, sliders, multi-select fields, etc).
content_url - If present, this is a URL to a piece of web content on Liferay to display at the top of the form. Useful for marketing collateral!
preferences_on_success - Remember the
preferences_gate defined for beacon events? If you successfully fill out the form, these
preference_on_success flags are "set". If you fail the form, the
preferences_on_fail preferences are set.
repeat - How many times to repeat the form. You might only want some forms fill-out-able once.
That's it! Using the above configuration, event staff can create the iBeacon interaction they desire, while avoiding annoyance as much as possible.
Each time the app thinks it wants to engage you with an iBeacon notification, it has to pass through a gauntlet of anti-annoyance checks to try to ensure you only get the messages you really should get, without being annoying. If any of these "fail", then nothing happens:
- Has it been long enough since your last notification? No? Fail.
- Are you currently busy with a previous notification (e.g. are you in the middle of filling a form?) Yes? Fail.
- Have you seen this notification enough times already? Yes? Fail.
- Has it been long enough since you last saw this particular notification? No? Fail.
- Is it the right day or time of day to show this notification? No? Fail.
- Have you told us not to show notifications from this location? Yes? Fail.
- Are all of the possible actions you could take as part of this notification invalid at this time? Yes? Fail.
As you can see, there are many types of anti-annoyance configuration elements (e.g. minimum repeat times, date/time filters, etc) and some built-in logic (e.g. global speed limit) that means you can't get flooded with beeps or vibrations every 10 seconds. It is highly recommended in your applications to avoid annoying users, because they will just get irritated and possibly tell all their friends about it.
Death Row - or how to deal with transient events
Imagine if you set up a "Welcome to the event!" message that shows when attendees first arrive at your event, and you also have a "Goodbye - thanks for attending!" message that should show when attendees leave your event. Now, imagine an attendee arrives at your event, and they get the welcome message. So far, so good. Now, imagine that before the opening keynote they have to make a pit stop at the WC, which is in the basement. While in the WC, your app no longer detects any beacons because you didn't put any there. When the attendee walks out of range of the beacons and into the WC, the app will think that they left the event and issue the "Goodbye" message, and they'd get rather confused and think they entered some kind of time warp to the end of the day. Further, when they return from the WC they might get the "Welcome!" message again! That's not going to do.
So instead of blanketing a space with hundreds of beacons to ensure 100% coverage, the app has a built-in death row. By adding a 15 minute delay (for example) to the "goodbye" message AND setting the
cancelable flag , when the attendee goes to the WC, the "Goodbye" message is queued to trigger in 15 minutes, as usual. When the attendee returns from the WC, the "Goodbye" message (which hasn't yet been triggered) is put on a software Death Row. If the attendee remains in the event, the "Goodbye" death sentence is carried out before the timeout expires, and the Goodbye message is "killed" (i.e. not shown). However, if the attendee then turns around and really does leave the event, the Goodbye message is "pardoned", taken off death row, and is allowed to occur after the specified delay.
This is akin to the "double bouncing" effect in electrical circuits. When a switch is closed, there is a momentary bouncing of the electrical signals as the two parts of the switch come together to complete the circuit. This can cause lights to flicker, or multiple forms to be submitted on a website, so logic is needed to wait until the switch is "really" closed with purpose, and this maps perfectly to this scenario - as attendees often "bounce" between indeterminate states (are you in the WC? Or in the event? Are you actually walking into a breakout session, or merely passing by?). Beacon-enabled apps should ensure that user actions (such as walking into a room) are intentional and not transient.
The app deals with data that isn't very sensitive, so if it is compromised, bridges won't fall and lights won't go out. But as a good practice we employed several basic security measures to protect overall integrity:
- Uses HTTPS everywhere
- Signed requests when writing data back to web services
- OAuth for external sites (e.g. Twitter, Facebook)
- Does not require login; usage is completely anonymous
The event app records a list of beacons your device can see every few minutes while the app runs, and passes this back Liferay, using another simple web service creating using Liferay's ServiceBuilder framework (see Privacy section below for details).
Android vs. iOS considerations
As for the hardware: Both Android 4.3+ and iOS 7.0+ support Bluetooth LE, on which iBeacons are based, but you also need hardware that implements it, such as an iPhone 4S or later, Galaxy S3/S4/S5, Nexus, and many others. There are other differences noted below that we discovered during the last several months, that might help you understand what to expect. We've tested across about 10 different Android devices (out of a possible 8,423 devices) and 7 different iOS devices:
- Accuracy - winner: iOS. We can confirm reports that iOS does a better job of reporting closer-to-reality proximity information (it also smoothes the data for you).
- Sensitivity - winner: Android - it can "see" for miles and miles and miles. We think it's because of the built-in smoothing of data in iOS and dealing with transient detection, which is not present (or just not as robust) on Android, which just tells you like it is, without any sugar coating (and we like it that way)
- Programming Model - winner: iOS. When we started this project, there were no free/libre/open source Titanium modules for interacting with iBeacons on Android, so we wrote one instead, using the awesome Radius Networks' beacon Library. iOS' LocationServices also seemed a more natural and higher-level abstraction than Android's lower-level Bluetooth APIs.
- Debuggability - winner: Android. Open source. 'nuff said.
- Backgrounding support - winner: Android (Barely). All you have to do when your app goes into the background is throttle down the Bluetooth LE scanning rate, but otherwise your app continues to run as it was written. With iOS, you have to do this ridiculous song and dance with a separately-scoped background "service" and seriously reduced feature set. Yeah, we know it is supposed to make multitasking better and keep app developers from crashing/killing your phone. But still, it's difficult to get the dance right between foreground and background. What makes iOS really nice here is even if your app is hard-closed, it is awoken when iBeacons are detected. But even that presents its own challenges.
- Robustness/lack of befuddling crashes and weird stack traces: Tie. Both produced the occasional wtf moments, but there wasn't a clear bias from one or the other for "Sharepoint moments".
- Power consumption - winner: iOS, but it might have been because we had tired batteries on Android. More rigorous test is probably needed.
Overall winner: "It Depends". It depends on which of these are more important to you. But iOS+Android make up like 99% of the market, so if you just target one or the other, you'll be missing out.
When iBeacons first emerged, they got a pretty bad rap, when phrases thrown around like "Marketing tools for the NSA". As in most cases, reality is much different than the fantasyland of the mass media, and iBeacons don't in and of themselves cause you to be tracked and reported to the authorities if you cross a double yellow line on the highway or steal toiletries from a hotel room. But they also don't prevent it :)
What does pose a danger are irresponsible (or malicious) app developers coupled with unsuspecting owners of smartphones who click on every link they see on Pinterest and give their private data to anyone that asks. This danger has existed since the mists of antiquity, it's just easier to do on a bigger scale now.
By default the Liferay Events app sends mostly-anonymous data about iBeacons back to our servers. The "mostly" bit is due to the fact that it also includes an "identifier", but that ID is associated with the installation of the app, not the person or the device. If you re-install the app, it gets a new ID. We feel this is a good way to it, and respect the privacy of the user. Oh and you can also switch off Bluetooth. Or uninstall the app!
There are a lot:
- Integration with liferay.com services (e.g. being able to log in, and link your event activity with your online profile)
- Detection of nearby interesting people, not just objects, and getting you to talk to them
- Auto-registration (pre-register in the app, and then when you walk up to the registration desk, the app emits a QR code that is scanned to confirm your presence)
- Rewards for attending certain types of sessions or tracks
- Pattern recognition for sessions, in order to suggest other, related sessions or people
- Realtime analytics, trendspotting
Computers and electronics in general continue their march toward smaller, faster, cheaper, more efficient, and more approachable footprints. Along with those advances comes new ways to apply them to solve problems that were historically difficult or impossible. And we are just getting started with mobile devices and apps, which open up a whole new class of applications that benefit not just the technically elite but also those that have until now been economically or socially disadvantaged.
iBeacon is one example of this, and in this author's opinion is a stepping stone to a really cool future involving location-aware computing and better, more personal engagement with our devices. Of course there will be missteps along the way and there are plenty of risks (privacy or lack thereof, securing transactions between moving people and things, EM radiation and interference, safety while operating heavy machinery, Skynet-type takeovers, and more), but this author is also optimistic that we can and will solve these issues. It's an interesting future! Thanks for reading!