Giochiamo con iBeacon
Cosa sono gli iBeacon e cosa puoi farci. Un piccolo esperimento con il kit Estimote: check-in automatico in ufficio appoggiandosi a un beacon, con backend Rails e client iOS.
Versione italiana dell’articolo originale del 1° luglio 2014; traduzione a cura del team.
Probabilmente sai già cos’è un iBeacon, ma giusto per ribadirlo: iBeacon è il modo Apple di dire “Bluetooth 4 Low Energy”. A costo di sembrare un automa entusiasta, per “modo Apple di” intendo “funziona e basta, ed ha un grosso potenziale”. Un iBeacon è un semplice dispositivo B4LE che continua a fare broadcast della propria presenza. Altri device B4LE possono accorgersi di entrare in prossimità del beacon senza scaricare la batteria (da qui l’LE) e senza far urlare di disperazione l’utente. “Ok, e cosa ci faccio?”. La cosa più interessante è poter localizzare un utente senza GPS, il che vuol dire localizzarlo dentro un edificio. Il bello è che è veloce: bastano secondi per rilevare un beacon e reagire alla sua presenza (o assenza), e funziona entro il raggio di copertura del Bluetooth (diciamo 50 metri). Va detto anche che funziona benissimo pure con Android.
Questa settimana è arrivato in ufficio un developer kit di Estimote, quindi abbiamo colto l’occasione per giocarci un po’.
Avevo già messo le mani sugli iBeacon in un passato non così lontano. Con BeaconEmitter puoi trasformare facilmente il tuo Mac in un beacon, senza hardware aggiuntivo. Quando avevo sperimentato con gli iBeacon avevo in mente un paio di idee che ruotavano intorno al poter inviare una local notification all’utente che entra nel raggio di un device che fa da beacon. I miei sogni si erano infranti contro i limiti dell’implementazione iOS 7.0, perché:
- non puoi reagire quando lo schermo dell’utente è spento
- non puoi eseguire alcuna azione mentre l’app è in background, anche se richiedi il background state
location - rilevare quando l’utente esce da una region richiede parecchio tempo (almeno 10-15 minuti)
La cosa più entusiasmante del giocare con l’SDK Estimote, a parte il packaging curato e l’hardware ben progettato, è che i miei device ora hanno iOS 7.1. A quanto pare con la versione 7.1 iOS è molto più flessibile e risolve tutti i problemi che avevo incontrato con la 7.0:
- puoi mostrare una local notification a schermo spento
- puoi eseguire operazioni quando l’utente entra in una region (anche se l’app è stata killata)
- bastano pochi secondi per rilevare quando l’utente esce dal raggio
Questo cambia tutto: gli iBeacon non sono più un gimmick, ma uno strumento interessante con cui sperimentare.
Building a sample
La prima cosa che ci è venuta in mente è stata costruire un semplice sistema per fare check-in e check-out automatici delle persone in ufficio. Davvero, banale: un paio d’ore di lavoro e funziona sorprendentemente bene.
Backend in Rails
Per fare check-in e check-out ci serve un backend e un sistema di autenticazione. Rails rende tutto facile: un model, una piccola API e l’aiuto di Devise per l’autenticazione.
# app/model/checkin.rb
class Checkin < ActiveRecord::Base
belongs_to :user
enum direction: { in: 0, out: 1 }
end
Un model piuttosto basico, che sfrutta gli enum di Rails 4.1.
Le route sono scope-ate come API, giusto per fare il fighi:
# config/routes.rb
namespace :api, defaults: {format: :json} do
namespace :v1 do
post 'checkin', to: 'checkins#checkin'
post 'checkout', to: 'checkins#checkout'
end
end
E il controller fa più o meno solo questo:
# checkins_controller.rb
def checkin
checkin = Checkin.new(user: current_user, direction: :in)
if checkin.save
head :no_content
else
render json: {errors: checkin.errors}, status: :unprocessable_entity
end
end
L’autenticazione è gestita da Devise e per semplicità abbiamo optato per HTTP Basic Authentication.
Client iOS
L’app iOS deve cercare il nostro fido beacon e, quando l’utente entra nel raggio della region, fare una chiamata POST alla nostra API. Quando l’utente esce dall’ufficio, il telefono deve fare lo stesso verso l’API di checkout. Le API iOS per gestire i beacon sono in CoreLocation; in questo sample userò due metodi delegate principali:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region;
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region;
Il bravo ragazzo che chiama questi due metodi è un’istanza di CLLocationManager. Il location manager ha però bisogno di un’istanza di CLBeaconRegion per iniziare a fare la sua magia. Possiamo definire una region specificando un UUID, un identifier, un major e un minor. Suona confuso all’inizio, ma in soldoni:
UUID: un identificativo univoco della nostra rete di beacon. La best practice è avere un UUID per ogni app. Tutti i beacon condividono lo stesso UUID.identifier: una rappresentazione stringa della nostra rete. Tipicamente è la reverse URI dell’app, qualcosa tipocom.something.awesome.major: un intero che identifica il gruppo principale dei nostri beacon. Pensalo come un numero condiviso che identifica un insieme di beacon dentro un edificio.minor: un intero che identifica il singolo beacon all’interno di un major group.
Quindi una configurazione tipo è: un UUID e un identifier per app, un major per edificio, un minor per beacon. In questo sample abbiamo un solo beacon, quindi possiamo ignorare queste informazioni o specificare major e minor a piacere, purché coincidano con quelli configurati sul beacon stesso. Detto questo, vediamo il codice:
- (CLBeaconRegion *)region
{
if (_region == nil) {
NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:self.settings[@"udid"]];
_region = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID
major:[self.settings[@"major"] intValue]
minor:[self.settings[@"minor"] intValue]
identifier:self.settings[@"identifier"]];
[_region setNotifyOnExit:YES];
[_region setNotifyOnEntry:YES];
[_region setNotifyEntryStateOnDisplay:YES];
}
return _region;
}
Ed eccoci: la nostra region in lazy loading che legge i parametri da un NSDictionary. Ottimo, iniziamo a monitorare:
[self.manager startMonitoringForRegion:self.region];
[self.manager stopRangingBeaconsInRegion:self.region];
La prima riga si spiega da sola, la seconda dice al sistema che non ci interessano i singoli beacon, ci servono solo gli aggiornamenti sulla region.
Ora che stiamo monitorando la region, dobbiamo decidere cosa fare quando entriamo o usciamo dal raggio:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
if ([beaconRegion.identifier isEqualToString:self.settings[@"identifier"]] && [beaconRegion.major intValue] == [self.settings[@"major"] intValue] && [beaconRegion.minor intValue]== [self.settings[@"minor"] intValue]) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.userInfo = @{@"identifier": region.identifier};
notification.alertBody = [NSString stringWithFormat:@"Entering %@", region.identifier];
notification.soundName = @"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
[self remoteCheckin:FPCheckDirectionIn];
}
}
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
if ([region isKindOfClass:[CLBeaconRegion class]]) {
CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region;
if ([beaconRegion.identifier isEqualToString:self.settings[@"identifier"]] && [beaconRegion.major intValue] == [self.settings[@"major"] intValue] && [beaconRegion.minor intValue]== [self.settings[@"minor"] intValue]) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.userInfo = @{@"identifier": region.identifier};
notification.alertBody = [NSString stringWithFormat:@"Exiting %@", region.identifier];
notification.soundName = @"Default";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
[self remoteCheckin:FPCheckDirectionOut];
}
}
}
Come vedi, stiamo solo controllando il nostro beacon: quando entriamo o usciamo dal raggio mandiamo una local notification e facciamo una chiamata remota al backend Rails. Trovi il source completo sul nostro account GitHub.
Who’s Fancy?
Ed eccoci, l’app iOS:

e la pagina web:

Trovi il codice Rails e iOS qui e qui.
Versione Android
Abbiamo pushato sulla nostra pagina GitHub anche la versione Android, la trovi qui.