Applicazioni sensibili al contesto e architetture complesse per l'elaborazione di eventi
Pubblicato: 2022-03-11L'utilizzo dei telefoni cellulari in tutto il mondo è in costante aumento. Nel 2013, circa il 73% degli utenti Internet ha consumato contenuti tramite un dispositivo mobile e si prevede che questa percentuale raggiungerà quasi il 90% entro il 2017.
Ci sono, ovviamente, molte ragioni per la rivoluzione mobile. Ma uno dei più significativi è che le app mobili generalmente accedono a un contesto più ricco poiché quasi tutti gli smartphone oggi sono dotati di sensori di posizione, sensori di movimento, bluetooth e wifi. Utilizzando i loro dati, le app possono ottenere una "consapevolezza del contesto" che può aumentare notevolmente le loro capacità e valore e può davvero farle risaltare negli app store.
In questo tutorial esploreremo la creazione di app sensibili al contesto attraverso un esempio di elaborazione di eventi complessi. Useremo un esempio abbastanza semplice: un'app per il prezzo del carburante che trova i migliori prezzi del carburante nella tua zona.
App sensibili al contesto
In Designing Calm Technology, Mark Weiser e John Seely Brown descrivono la tecnologia calma come "ciò che informa ma non richiede la nostra concentrazione o attenzione".
Le app mobili sensibili al contesto sono altamente coerenti con questa nozione e rappresentano un passo importante e prezioso lungo questo percorso. Utilizzano le informazioni contestuali raccolte dai loro sensori per fornire in modo proattivo all'utente informazioni preziose e lo fanno con il minimo sforzo da parte dell'utente. Mark Weiser e John Seely Brown applaudirebbero senza dubbio questo progresso tecnologico.
La consapevolezza del contesto è l'idea che un'app può rilevare e reagire in base ai dati contestuali a cui ha accesso. Tale app utilizza i dati dei sensori avanzati disponibili su un dispositivo mobile per fornire informazioni accurate e pertinenti all'utente nel contesto appropriato. Attraverso le tendenze che osserva nel corso dell'utilizzo del dispositivo e/o attraverso il feedback fornito dall'utente, tale app può effettivamente "imparare" nel tempo, diventando così più "intelligente" e più utile.
Elaborazione di eventi complessi
L'elaborazione di eventi complessi (CEP) è una forma di elaborazione di eventi che impiega analisi più sofisticate di eventi multipli (cioè, nel tempo, da diverse fonti e così via), integrando e analizzando il loro contenuto per dedurre informazioni e modelli più significativi.
In un'app mobile, il CEP può essere applicato agli eventi generati dai sensori del dispositivo mobile e alle origini dati esterne a cui l'app ha accesso.
Caratteristiche principali della nostra app per il prezzo del carburante
Ai fini del nostro complesso tutorial sull'elaborazione degli eventi, supponiamo che le funzionalità della nostra app per il prezzo del carburante siano limitate a quanto segue:
- rilevamento automatico delle posizioni geograficamente rilevanti per l'utente (ad esempio, la posizione di casa dell'utente e la posizione di lavoro dell'utente)
- identificare automaticamente le stazioni di servizio entro una distanza ragionevole dalla casa e dal luogo di lavoro dell'utente
- notificando automaticamente all'utente i migliori prezzi del carburante vicino a casa e al lavoro
OK, iniziamo.
Rilevamento delle posizioni di casa e di lavoro dell'utente
Cominciamo con la logica per il rilevamento automatico delle postazioni di casa e di lavoro dell'utente. Al fine di semplificare le cose per il nostro esempio di elaborazione di eventi complessi, assumeremo che l'utente abbia un programma di lavoro abbastanza normale. Possiamo quindi presumere che l'utente sarà in genere a casa tra le 2:00 e le 3:00 e in genere sarà in ufficio tra le 14:00 e le 15:00.
Sulla base di questi presupposti, definiamo due regole CEP e raccogliamo i dati sulla posizione e l'ora dallo smartphone dell'utente:
- Regola della posizione di casa
- raccogliere i dati sulla posizione tra le 2 e le 3 del mattino per una settimana
- raggruppare i dati sulla posizione per ottenere l'indirizzo di casa approssimativo
- Regola della posizione di lavoro
- raccogliere i dati sulla posizione tra le 14 e le 15 per i giorni feriali
- raggruppare i dati sulla posizione per ottenere la posizione di lavoro approssimativa
L'algoritmo di alto livello per rilevare le posizioni è illustrato di seguito.
Assumiamo la seguente semplice struttura di dati JSON per i dati di posizione:
{ "uid": "some unique identifier for device/user", "location": [longitude, latitude] "time": "time in user's timezone" }
Nota: è sempre buona norma rendere immutabili i dati del sensore (o il tipo di valore), in modo che possano essere utilizzati in sicurezza da diversi moduli nel flusso di lavoro CEP.
Implementazione
Implementeremo il nostro algoritmo utilizzando un modello di modulo componibile , in base al quale ogni modulo esegue solo un'attività e chiama successivamente quando l'attività è completa. Questo è conforme alla filosofia Unix Rule of Modularity.
Nello specifico, ogni modulo è una funzione che accetta un oggetto di config
e una funzione next
che viene chiamata per passare i dati al modulo successivo. Di conseguenza, ogni modulo restituisce una funzione in grado di accettare i dati del sensore. Ecco la firma di base di un modulo:
// nominal structure of each composable module function someModule(config, next) { // do initialization if required return function(data) { // do runtime processing, handle errors, etc. var nextData = SomeFunction(data); // optionally call next with nextData next(nextData); } }
Per implementare il nostro algoritmo per dedurre le posizioni di casa e di lavoro dell'utente, avremo bisogno dei seguenti moduli:
- Modulo filtro tempo
- Modulo accumulatore
- Modulo di raggruppamento
Ciascuno di questi moduli è descritto più dettagliatamente nelle sottosezioni che seguono.
Modulo filtro tempo
Il nostro filtro temporale è una semplice funzione che prende come input gli eventi dei dati sulla posizione e passa i dati al modulo next
solo se l'evento si è verificato entro l'intervallo di tempo di interesse. I dati di config
per questo modulo sono quindi costituiti dall'ora di inizio e di fine dell'intervallo di tempo di interesse. (Una versione più sofisticata del modulo potrebbe filtrare in base a più intervalli di tempo.)
Ecco un'implementazione pseudocodice del modulo filtro temporale:
function timeFilter(config, next) { function isWithin(timeval) { // implementation to compare config.start <= timeval <= config.end // return true if within time slice, false otherwise } return function (data) { if(isWithin(data.time)) { next(data); } }; }
Modulo accumulatore
La responsabilità dell'accumulatore è semplicemente quella di raccogliere i dati sulla posizione per poi essere passati al modulo next
. Questa funzione mantiene un bucket interno di dimensioni fisse per archiviare i dati. Ogni nuova posizione incontrata viene aggiunta al bucket finché il bucket non è pieno. I dati sulla posizione accumulati nel bucket vengono quindi inviati al modulo successivo come array.
Sono supportati due tipi di secchi dell'accumulatore. Il tipo di bucket influisce sulle operazioni eseguite sul contenuto del bucket dopo che i dati sono stati inoltrati alla fase successiva, come segue:
Secchio finestra burattata (
type = 'tumbling'
): dopo aver inoltrato i dati, svuota l'intero bucket e ricomincia da capo (dimensione del bucket ridotta a 0)Tipo di finestra in esecuzione (
type = 'running'
): dopo aver inoltrato i dati, elimina solo l'elemento di dati più vecchio nel bucket (riduce la dimensione del bucket di 1)
Ecco un'implementazione di base del modulo accumulatore:
function accumulate(config, next) { var bucket = []; return function (data) { bucket.unshift(data); if(bucket.length >= config.size) { var newSize = (config.type === 'tumbling' ? 0 : bucket.length - 1); next(bucket.slice(0)); bucket.length = newSize; } }; }
Modulo di raggruppamento
Esistono ovviamente molte tecniche sofisticate nella geometria delle coordinate per raggruppare i dati 2D. Ecco un modo semplice per raggruppare i dati sulla posizione:
- trova vicini per ogni posizione in un insieme di posizioni
- se alcuni dei neighbor appartengono a un cluster esistente, espandere neighbor con cluster
- se le posizioni nel set di vicini sono superiori alla soglia, aggiungi i vicini come un nuovo cluster
Ecco un'implementazione di questo algoritmo di clustering (usando Lo-Dash
):
var _ = require('lodash'); function createClusters(location_data, radius) { var clusters = []; var min_points = 5; // Minimum cluster size function neighborOf(this_location, all_locations) { return _.filter(all_locations, function(neighbor) { var distance = distance(this_point.location, neighbor.location); // maximum allowed distance between neighbors is 500 meters. return distance && (500 > distance); } } _.each(location_data, function (loc_point) { // Find neighbors of loc_point var neighbors = neighborOf(loc_point, location_data, radius); _.each(clusters, function (cluster, index) { // Check whether some of the neighbors belong to cluster. if(_.intersection(cluster, neighbors).length){ // Expand neighbors neighbors = _.union(cluster, neighbors); // Remove existing cluster. We will add updated cluster later. clusters[index] = void 0; } }); if(neighbors.length >= min_points){ // Add new cluster. clusters.unshift(neighbors); } }); return _.filter(clusters, function(cluster){ return cluster !== void 0; }); }
Il codice sopra presuppone l'esistenza di una funzione distance()
che calcola la distanza (in metri) tra due posizioni geografiche. Accetta due punti di localizzazione sotto forma di [longitude, latitude]
e restituisce la distanza tra di loro. Ecco un esempio di implementazione di tale funzione:
function distance(point1, point2) { var EARTH_RADIUS = 6371000; var lng1 = point1[0] * Math.PI / 180; var lat1 = point1[1] * Math.PI / 180; var lng2 = point2[0] * Math.PI / 180; var lat2 = point2[1] * Math.PI / 180; var dLat = lat2 - lat1; var dLon = lng2 - lng1; var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); var arc = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); var distance = EARTH_RADIUS * arc; return distance; }
Con il nostro algoritmo di clustering definito e implementato (nella nostra funzione createClusters()
mostrata in precedenza), possiamo usarlo come base per il nostro modulo di clustering:
function clusterize(config, next) { return function(data) { var clusters = createClusters(data, config.radius); next(clusters); }; }
Mettendo tutto insieme
Tutte le funzioni dei componenti richieste sono ora definite, quindi siamo pronti per codificare le nostre regole di posizione di casa/lavoro.
Ecco, ad esempio, una possibile implementazione della regola della posizione di casa:
var CLUSTER_RADIUS = 150; // use cluster radius of 150 meters var BUCKET_SIZE = 500; // collect 500 location points var BUCKET_TYPE = 'tumbling'; // use a tumbling bucket in our accumulator var home_cluster = clusterize({radius: CLUSTER_RADIUS}, function(clusters) { // Save clusters in db }); var home_accumulator = accumulate({size: BUCKET_SIZE, type: BUCKET_TYPE}, home_cluster); var home_rule = timeFilter({start: "2AM", end: "3AM"}, home_accumulator);
Ora, ogni volta che i dati sulla posizione vengono ricevuti dallo smartphone (tramite websocket, TCP, HTTP) inoltriamo questi dati alla funzione home_rule
che a sua volta rileva i cluster per la casa dell'utente.
Si presume quindi che la "posizione principale" dell'utente sia il centro del cluster della posizione iniziale.
Nota: anche se questo potrebbe non essere del tutto preciso, è adeguato per il nostro semplice esempio, soprattutto perché l'obiettivo di questa app in ogni caso è semplicemente quello di conoscere l'area circostante la casa dell'utente, piuttosto che conoscere l'esatta posizione di casa dell'utente.
Ecco una semplice funzione di esempio che calcola il "centro" di un insieme di punti in un cluster in media le latitudini e le longitudini di tutti i punti nel set di cluster:
function getCentre(cluster_data) { var len = cluster_data.length; var sum = _.reduce(cluster_data, function(memo, cluster_point){ memo[0] += cluster_point[0]; memo[1] += cluster_point[1]; return memo; }, [0, 0]); return [sum[0] / len, sum[1] / len]; }
Un approccio simile potrebbe essere impiegato per dedurre il luogo di lavoro, con l'unica differenza che utilizzerebbe un filtro temporale tra le 14 e le 15 (invece che tra le 2 e le 3 del mattino).
La nostra app carburante è quindi in grado di rilevare automaticamente la posizione di lavoro e di casa dell'utente senza richiedere alcun intervento da parte dell'utente. Questo è l'informatica sensibile al contesto al massimo!
Trovare stazioni di servizio nelle vicinanze
Il duro lavoro per stabilire la consapevolezza del contesto è stato ora fatto, ma abbiamo ancora bisogno di un'altra regola per identificare i prezzi delle stazioni di servizio da monitorare (ad esempio, quali stazioni di servizio sono abbastanza vicine alla casa o al luogo di lavoro dell'utente per essere rilevanti). Questa regola richiede l'accesso a tutte le posizioni delle stazioni di servizio per tutte le regioni supportate dall'app carburante. La regola è la seguente:
- Regola delle stazioni di servizio
- trova le stazioni di servizio più vicine per ogni casa e luogo di lavoro
Questo può essere facilmente implementato utilizzando la funzione distanza mostrata in precedenza come filtro di posizione da applicare a tutte le stazioni di servizio note all'app.
Monitoraggio dei prezzi del carburante
Una volta che l'app carburante ottiene l'elenco delle stazioni di servizio preferite (cioè vicine) per l'utente, può facilmente cercare i migliori prezzi del carburante in queste stazioni. Può anche avvisare l'utente quando una di queste stazioni di servizio ha prezzi o offerte speciali, specialmente quando l'utente viene rilevato per essere vicino a queste stazioni di servizio.
Conclusione
In questo complesso tutorial sull'elaborazione degli eventi, abbiamo a malapena scalfito la superficie dell'elaborazione sensibile al contesto.
Nel nostro semplice esempio, abbiamo aggiunto il contesto della posizione a un'app di segnalazione dei prezzi del carburante altrimenti semplice e l'abbiamo resa più intelligente. L'app ora si comporta in modo diverso su ogni dispositivo e nel tempo rileva i modelli di posizione per migliorare automaticamente il valore delle informazioni che fornisce ai suoi utenti.
Sicuramente è possibile aggiungere molti più dati logici e di sensori per aumentare la precisione e l'utilità della nostra app sensibile al contesto. Uno sviluppatore mobile intelligente potrebbe, ad esempio, utilizzare i dati dei social network, i dati meteorologici, i dati sulle transazioni dei terminali POS e così via per aggiungere ancora più consapevolezza al contesto alla nostra app e renderla più praticabile e commerciabile.
Con l'elaborazione sensibile al contesto, le possibilità sono infinite. Sempre più app intelligenti continueranno ad apparire negli app store che utilizzano questa potente tecnologia per semplificarci la vita.