Aplicaciones sensibles al contexto y arquitectura de procesamiento de eventos complejos

Publicado: 2022-03-11

El uso de teléfonos móviles en todo el mundo aumenta constantemente. A partir de 2013, alrededor del 73% de los usuarios de Internet consumieron contenido a través de un dispositivo móvil y se espera que este porcentaje alcance cerca del 90% para 2017.

Hay, por supuesto, muchas razones para la revolución móvil. Pero uno de los más significativos es que las aplicaciones móviles generalmente obtienen acceso a un contexto más rico, ya que casi todos los teléfonos inteligentes de hoy en día están equipados con sensores de ubicación, sensores de movimiento, bluetooth y wifi. Al hacer uso de sus datos, las aplicaciones pueden lograr una "conciencia del contexto" que puede aumentar drásticamente sus capacidades y valor, y realmente puede hacer que se destaquen en las tiendas de aplicaciones.

En este tutorial, exploraremos la creación de aplicaciones sensibles al contexto a través de un ejemplo de procesamiento de eventos complejos. Usaremos un ejemplo bastante simple: una aplicación de precios de combustible que encuentra los mejores precios de combustible en su área.

La conciencia del contexto en las aplicaciones se puede crear a través de un tutorial de procesamiento de eventos complejo como este.

Aplicaciones conscientes del contexto

En Designing Calm Technology, Mark Weiser y John Seely Brown describen la tecnología tranquila como "aquello que informa pero no exige nuestro enfoque o atención".

Las aplicaciones móviles conscientes del contexto son muy consistentes con esta noción y son un paso importante y valioso en este camino. Emplean información contextual recopilada de sus sensores para proporcionar proactivamente al usuario información valiosa y lo hacen con un mínimo esfuerzo por parte del usuario. Sin duda, Mark Weiser y John Seely Brown aplaudirían este avance tecnológico.

La conciencia del contexto es la idea de que una aplicación puede detectar y reaccionar en función de los datos contextuales a los que tiene acceso. Una aplicación de este tipo utiliza datos de sensores enriquecidos que están disponibles en un dispositivo móvil para proporcionar información precisa y relevante al usuario en el contexto adecuado. A través de las tendencias que observa en el transcurso del uso del dispositivo y/o a través de los comentarios proporcionados por el usuario, una aplicación de este tipo puede realmente "aprender" con el tiempo, por lo que se vuelve "más inteligente" y más útil.

Procesamiento de eventos complejos

El procesamiento de eventos complejos (CEP) es una forma de procesamiento de eventos que emplea análisis más sofisticados de múltiples eventos (es decir, a lo largo del tiempo, de diferentes fuentes, etc.), integrando y analizando su contenido para deducir información y patrones más significativos.

En una aplicación móvil, CEP se puede aplicar a eventos generados por los sensores del dispositivo móvil, así como a fuentes de datos externas a las que tiene acceso la aplicación.

Características clave de nuestra aplicación de precios de combustible

A los fines de nuestro tutorial de procesamiento de eventos complejos, supongamos que las características de nuestra aplicación de precios de combustible se limitan a lo siguiente:

  • detectar automáticamente ubicaciones que son geográficamente relevantes para el usuario (por ejemplo, la ubicación de la casa del usuario y la ubicación del trabajo del usuario)
  • identificar automáticamente las estaciones de combustible dentro de una distancia razonable de la casa y el lugar de trabajo del usuario
  • notificando automáticamente al usuario de los mejores precios de combustible cerca de casa y el trabajo

Bien, comencemos.

Detección de las ubicaciones de casa y trabajo del usuario

Comencemos con la lógica para detectar automáticamente las ubicaciones de casa y trabajo del usuario. Para simplificar las cosas en nuestro ejemplo de procesamiento de eventos complejos, supondremos que el usuario tiene un horario de trabajo bastante normal. Por lo tanto, podemos suponer que el usuario normalmente estará en casa entre las 2 y las 3 de la mañana, y normalmente estará en su oficina entre las 2 y las 3 de la tarde.

Con base en esos supuestos, definimos dos reglas CEP y recopilamos datos de ubicación y hora del teléfono inteligente del usuario:

  • Regla de ubicación de inicio
    • recopilar datos de ubicación entre las 2 y las 3 a. m. durante una semana
    • agrupar los datos de ubicación para obtener la dirección de casa aproximada

  • Regla de ubicación de trabajo
    • recopilar datos de ubicación entre las 2 y las 3 p. m. para los días de semana
    • agrupar los datos de ubicación para obtener la ubicación aproximada del trabajo

El algoritmo de alto nivel para detectar ubicaciones se muestra a continuación.

Este diagrama demuestra cómo funcionará la aplicación contextual de este tutorial.

Supongamos la siguiente estructura de datos JSON simple para datos de ubicación:

 { "uid": "some unique identifier for device/user", "location": [longitude, latitude] "time": "time in user's timezone" }

Nota: Siempre es una buena práctica hacer que los datos del sensor sean inmutables (o tipo de valor), para que puedan ser utilizados de forma segura por diferentes módulos en el flujo de trabajo de CEP.

Implementación

Implementaremos nuestro algoritmo utilizando un patrón de módulo componible , en el que cada módulo realiza solo una tarea y llama a continuación cuando la tarea está completa. Esto se ajusta a la filosofía de la regla de modularidad de Unix.

Específicamente, cada módulo es una función que acepta un objeto de config y una next función que se llama para pasar los datos al siguiente módulo. En consecuencia, cada módulo devuelve una función que puede aceptar datos de sensores. Aquí está la firma básica de un módulo:

 // 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); } }

Para implementar nuestro algoritmo para deducir las ubicaciones de la casa y el trabajo del usuario, necesitaremos los siguientes módulos:

  • Módulo de filtro de tiempo
  • Módulo acumulador
  • Módulo de agrupamiento

Cada uno de estos módulos se describe con más detalle en las subsecciones siguientes.

Módulo de filtro de tiempo

Nuestro filtro de tiempo es una función simple que toma eventos de datos de ubicación como entrada y solo pasa datos al next módulo si el evento ocurrió dentro del intervalo de tiempo de interés. Por lo tanto, los datos de config de este módulo consisten en las horas de inicio y finalización del intervalo de tiempo de interés. (Una versión más sofisticada del módulo podría filtrar en función de múltiples intervalos de tiempo).

Aquí hay una implementación de pseudocódigo del módulo de filtro de tiempo:

 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); } }; }

Módulo acumulador

La responsabilidad del acumulador es simplemente recopilar datos de ubicación para luego pasarlos al next módulo. Esta función mantiene un cubo interno de tamaño fijo para almacenar datos. Cada nueva ubicación encontrada se agrega al depósito hasta que se llena. Los datos de ubicación acumulados en el depósito se envían al siguiente módulo como una matriz.

Se admiten dos tipos de cubetas de acumuladores. El tipo de depósito afecta lo que se hace con el contenido del depósito después de que los datos se envían a la siguiente fase, de la siguiente manera:

  • Cubo de ventana de caída ( type = 'tumbling' ): después de reenviar datos, vacía el cubo completo y comienza de nuevo (tamaño de cubo reducido a 0)

  • Tipo de ventana en ejecución ( type = 'running' ): después de reenviar los datos, solo descarta el elemento de datos más antiguo en el depósito (reduce el tamaño del depósito en 1)

Aquí hay una implementación básica del módulo acumulador:

 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; } }; }

Módulo de agrupamiento

Por supuesto, existen muchas técnicas sofisticadas en geometría de coordenadas para agrupar datos 2D. Aquí hay una forma simple de agrupar datos de ubicación:

  • encontrar vecinos para cada ubicación en un conjunto de ubicaciones
  • si algunos de los vecinos pertenecen a un clúster existente, expanda los vecinos con el clúster
  • si las ubicaciones en el conjunto de vecinos superan el umbral, agregue vecinos como un nuevo clúster

Aquí hay una implementación de este algoritmo de agrupamiento (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; }); }

El código anterior asume la existencia de una función de distance() que calcula la distancia (en metros) entre dos ubicaciones geográficas. Acepta dos puntos de ubicación en forma de [longitude, latitude] y devuelve la distancia entre ellos. Aquí hay una implementación de muestra de tal función:

 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 nuestro algoritmo de agrupamiento definido e implementado (en nuestra función createClusters() que se mostró anteriormente), podemos usarlo como base para nuestro módulo de agrupamiento:

 function clusterize(config, next) { return function(data) { var clusters = createClusters(data, config.radius); next(clusters); }; }

Tirando de todo junto

Todas las funciones de componentes requeridas ahora están definidas, por lo que estamos listos para codificar nuestras reglas de ubicación de casa/trabajo.

Aquí, por ejemplo, hay una posible implementación de la regla de ubicación de inicio:

 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);

Ahora, cada vez que se reciben datos de ubicación desde un teléfono inteligente (a través de websocket, TCP, HTTP), reenviamos estos datos a la función home_rule , que a su vez detecta grupos para el hogar del usuario.

Entonces se asume que la “ubicación de inicio” del usuario es el centro del grupo de ubicación de inicio.

Nota: si bien esto puede no ser del todo preciso, es adecuado para nuestro ejemplo simple, especialmente porque el objetivo de esta aplicación en cualquier caso es simplemente conocer el área que rodea la casa del usuario, en lugar de conocer la ubicación precisa de la casa del usuario.

Aquí hay una función de ejemplo simple que calcula el "centro" de un conjunto de puntos en un grupo mediante el promedio de las latitudes y longitudes de todos los puntos en el conjunto de grupos:

 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]; }

Se podría emplear un enfoque similar para deducir la ubicación del trabajo, con la única diferencia de que usaría un filtro de tiempo entre las 2 y las 3 p. m. (en lugar de las 2 y las 3 a. m.).

Por lo tanto, nuestra aplicación de combustible puede detectar automáticamente las ubicaciones de trabajo y hogar del usuario sin requerir ninguna intervención del usuario. ¡Esto es computación consciente del contexto en su máxima expresión!

Encontrar gasolineras cercanas

Ya se ha hecho el trabajo duro para establecer la conciencia del contexto, pero aún necesitamos una regla más para identificar los precios de las estaciones de combustible que se deben monitorear (es decir, qué estaciones de combustible están lo suficientemente cerca de la casa o el lugar de trabajo del usuario para ser relevantes). Esta regla necesita acceso a todas las ubicaciones de las estaciones de combustible para todas las regiones admitidas por la aplicación de combustible. La regla es la siguiente:

  • Regla de la estación de combustible
    • encuentre las estaciones de combustible más cercanas para cada lugar de trabajo y hogar

Esto se puede implementar fácilmente utilizando la función de distancia que se muestra anteriormente como un filtro de ubicación para aplicar a todas las estaciones de servicio conocidas por la aplicación.

Las aplicaciones conscientes del contexto se vuelven más inteligentes con el tiempo, como esta aplicación de tutorial.

Seguimiento de los precios de los combustibles

Una vez que la aplicación de combustible obtiene la lista de estaciones de combustible preferidas (es decir, cercanas) para el usuario, puede buscar fácilmente los mejores precios de combustible en estas estaciones. También puede notificar al usuario cuando una de estas estaciones de combustible tiene precios u ofertas especiales, especialmente cuando se detecta que el usuario está cerca de estas estaciones de combustible.

Este complejo tutorial de procesamiento de eventos demuestra cómo se puede crear conciencia de contexto en una aplicación.

Conclusión

En este tutorial de procesamiento de eventos complejos, apenas hemos arañado la superficie de la computación consciente del contexto.

En nuestro ejemplo simple, agregamos contexto de ubicación a una aplicación de informes de precios de combustible simple y la hicimos más inteligente. La aplicación ahora se comporta de manera diferente en cada dispositivo y, con el tiempo, detecta los patrones de ubicación para mejorar automáticamente el valor de la información que proporciona a sus usuarios.

Seguramente se pueden agregar muchos más datos lógicos y de sensores para aumentar la precisión y la utilidad de nuestra aplicación consciente del contexto. Un desarrollador móvil inteligente podría, por ejemplo, hacer uso de datos de redes sociales, datos meteorológicos, datos de transacciones de terminales POS, etc., para agregar aún más conciencia del contexto a nuestra aplicación y hacerla más viable y comercializable.

Con la computación consciente del contexto, las posibilidades son infinitas. Más y más aplicaciones inteligentes seguirán apareciendo en las tiendas de aplicaciones que emplean esta poderosa tecnología para simplificar nuestras vidas.