scieee AI-readable full text Open interactive document viewer

Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos

Mayor Gallego, Vicente Jesús

Abstract

Este documento recoge el estudio y desarrollo de una herramienta distribuida para el seguimiento de una flota de vehículos. En él, se analiza el mercado actual y se propone una solución alternativa de bajo coste para que las empresas con menos recursos económicos puedan llevar a cabo monitorizaciones de este tipo y tengan un control total sobre su inventario de transporte. Esta solución pasa por usar los teléfonos móviles de los empleados como elementos agentes que enviarán su posición de manera inteligente cuando sea necesario, evitando así el uso de localizadores GPS de uso específico que son, junto con su mantenimiento, los responsables del alto sacrificio económico que suponen otras alternativas

Full text

Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Autor: Vicente Jesús Mayor Gallego Tutor: Antonio José Estepa Alonso Dep. de Ingeniería Telemática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2015 ii iii Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías de Telecomunicación Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Autor: Vicente Jesús Mayor Gallego Tutor: Antonio José Estepa Alonso Profesor titular Dep. de Ingeniería Telemática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2015 iv v Trabajo Fin de Grado: Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Autor: Vicente Jesús Mayor Gallego Tutor: Antonio José Estepa Alonso El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2015 El Secretario del Tribunal vi vii A mi familia A mis maestros viii ix Agradecimientos En primer lugar, me gustaría agradecer a mi tutor, Antonio, por haberme dado la oportunidad de trabajar en este proyecto, así como a todos los maestros y profesores que me han ido formando desde que era pequeño hasta hoy. En segundo lugar, agradecer a todos aquellos que me han ayudado en tareas de depuración y pruebas de la herramienta. También quiero agradecer a mis padres por apoyarme en todo momento, aconsejarme lo mejor posible y depositar una enorme confianza en mi, brindándome la oportunidad de estudiar lo que me gusta. Y por último, a mi pareja por su paciencia y cariño, animándome y apoyándome cada vez que lo he necesitado. Todos formáis parte de este trabajo y sin vosotros no hubiera sido posible, muchas gracias. Vicente Jesús Mayor Gallego Sevilla, 2015 xvi Figura 6-8. Índice de la página web 59! Figura 6-9. Conductores en la página Web 59! Figura 6-10. Seleccionador de fecha y hora en la página web 60! Figura 6-11. Activada la vista detallada en la página web 60! Figura 6-12. Ficha detallada de una sesión en la página web 61! Figura 6-13. Sección de administración en la página web 62! Figura 7-1. Registro de usuarios y vehículos 69! Figura 7-2. Ajustes en la aplicación móvil 70! Figura 7-3. Entrada en lista de sesiones. 70! Figura 7-4. Seguimiento en tiempo real en el cliente. 71! Figura 7-5. Seguimiento en tiempo real en el servidor. 71! Figura 7-6. Sesión completa en el cliente 71! Figura 7-7. Sesión completa en el servidor 71! Figura 7-8. Detalles de una sesión 72! Figura 7-9. Gráficas de una sesión 72! Figura 7-10. Ficha detallada de un conductor 73! Figura 7-11. Visualización de todos los conductores en el mapa. 74! Figura 7-12. Vista general de sesiones 74! Figura 7-13. Filtrado de sesiones por fechas 75! Figura 7-14. Filtrado detallado de una sesión 75! Figura 7-15. Informe generado en hoja de cálculo 75! Figura 9-1. Diagrama de clases del cliente 86! xvii 1 1 INTRODUCCIÓN n la actualidad, existen muchas empresas que ponen al servicio de sus trabajadores un conjunto de vehículos para que puedan realizar sus labores profesionales, como por ejemplo los negocios de transporte o aquellos que ofrezcan cualquier tipo de servicio a domicilio. Sin embargo, el rendimiento obtenido de esta flota de vehículos puede verse mermado si no seguimos algunas prácticas de gestión o administración. Resulta complejo gestionar estos elementos sin una correcta monitorización y este control no es sencillo si no utilizamos herramientas automatizadas. Por desgracia, en la mayoría de los casos estas herramientas son de elevado coste y además, requieren la compra e instalación de algún dispositivo de uso específico, es decir, un localizador GPS. La mayoría de las pequeñas y medianas empresas no disponen de vehículos ni recursos económicos suficientes como para que la instalación de estos dispositivos sea rentable y, por tanto, muchas deciden no utilizar herramientas modernas para la monitorización de su flota de vehículos. La monitorización remota automatizada de vehículos puede ser muy útil no sólo para la administración de los mismos, sino incluso para los propios conductores dentro de la empresa; pongamos un ejemplo: Supongamos que en una empresa de telecomunicaciones se detecta una avería en un punto de la red, si conocemos la ubicación de todos los vehículos de la empresa, se podría asignar la tarea de revisar esa avería al técnico capacitado más cercano a ese punto, ahorrando tiempo y combustible y, en conclusión, ofreciendo un servicio más eficiente. Entre los objetivos de este seguimiento no sólo se encuentra la ubicación en tiempo real, sino que además recolectaremos la siguiente información: distancia y velocidad media de conductores y vehículos, horas totales de circulación, contabilidad de incidencias de los conductores, uso indebido de los vehículos, etc. Una vez recolectada toda esta información, se podrán utilizar como base en la aplicación de medidas correctivas con el objetivo de mejorar la eficiencia. Por ejemplo, en una empresa de reparto a domicilio se podría estudiar la puntualidad de las entregas para poder mejorar el servicio o distribuir de manera más eficiente el personal. Como conclusión, mediante una correcta monitorización podríamos obtener una serie de ventajas: • Aumento de la productividad al permitir una mejor planificación y asignación de los conductores a sus vehículos, así como su monitorización en tiempo real. • Reducción de los costes, ya que una mayor productividad y eficiencia de las sesiones de conducción permite que el combustible consumido sea menor. • Aumento de la seguridad y calidad del servicio prestado ya que se podrán realizar estudios acerca de la puntualidad de los conductores, así como de su estilo de conducción. Por todo lo expuesto anteriormente, este documento tratará el diseño y desarrollo de una herramienta de monitorización de vehículos, con el objetivo de solucionar los problemas de coste y flexibilidad que las alternativas actuales presentan. E 3 2 MERCADO ACTUAL ntes de comenzar a definir nuestra propuesta, es necesario buscar información acerca de las soluciones que se ofrecen de manera comercial en el mercado actual. En esta sección nos centraremos en algunos de los servicios que se ofrecen actualmente para cubrir esta necesidad [1]. 2.1 Movistar y TomTom En primer lugar, estudiemos el servicio que ofrece Movistar junto con TomTom [2]. Dentro de este servicio de nombre Seguimiento GPS, encontramos tres modalidades distintas. • En la primera de las soluciones, llamada Seguimiento CarClick, el agente es un localizador por puerto OBD II sólo válido para turismos y furgonetas de menos de 3.500 kg. Posee un precio de alta de 95,59€1 y una cuota mensual de 27,71€ por licencia, ofreciendo descuentos por volumen de licencias. • En la segunda solución, llamada Seguimiento Flotas, es necesario instalar un localizador embarcado (Modelo MTX65) siendo su precio por unidad de 175,45€ y una cuota mensual de 26,50€ por licencia ofreciendo también descuentos por volumen de licencias. • Como solución más económica, aunque no identifique vehículos sino conductores, existe una alternativa llamada Seguimiento Móviles consistente en la instalación de una aplicación en un terminal móvil para su seguimiento. Su cuota mensual por licencia es de 20,45€2 y es combinable con las otras dos soluciones para obtener un seguimiento real de vehículos y personas. En cuanto a las operaciones de consulta de la información recolectada que se permiten, destacamos las siguientes. • Localización de la flota y visualización de trayectos. • Creación de informes. • Gestión de zonas. • Visualización de tramos conducidos con exceso de velocidad respecto a la vía. • Creación de alertas. Tal y como se dice en la página web, estos servicios cubren las necesidades básicas de localización y gestión de una flota, aunque sus restricciones pueden resultar una dificultad para ciertas empresas. • Es necesario ser cliente de Telefónica. • Las dos primeras soluciones ofrecen seguimiento de vehículos mientras que la tercera ofrece seguimiento de usuarios, por lo tanto, la única manera para monitorizar ambos simultáneamente es la combinación de ambos servicios, siendo el coste resultante muy elevado. 2.2 Verizon Otra compañía reconocida que ofrece un servicio similar es Verizon [3]. El servicio viene bajo el nombre NetworkFleet y se propone como un sistema de seguimiento de flotas asequible y sencillo de usar. En cuanto a los precios no se encuentra demasiada información en su página web. Únicamente se menciona que el precio base es 1$ por día y por vehículo. Respecto al dispositivo utilizado como agente, se utiliza un localizador GPS conectado por el puerto OBD-II. 1!IVA$incluido en los precios" " A Mercado actual 4" Las principales ventajas que se anuncian en su web son las siguientes: • Robustez del sistema, es decir, se pueden consultar los datos en cualquier momento. • Independiente de software, se accederá a través de un navegador web. • Un sistema de organización y mantenimiento potente de la información de los vehículos almacenados. • Se permite la configuración de alarmas. • Asistencia en carretera sin coste adicional. Como inconveniente encontramos que a priori no se identifica al conductor en el seguimiento y al requerir un dispositivo físico para su funcionamiento, el precio puede resultar elevado en algunos casos aunque la asistencia en carretera proporcionada pueda equilibrar la balanza. 2.3 Fleetistics Una solución mucho más completa que las anteriores es el servicio ofrecido por Fleetistics [4]. El objetivo de este servicio, y así lo publicitan en su web, es la mejora del ROI de la empresa consumidora y para ello ofrecen, entre otras, las siguientes ventajas: • Sencillez de instalación y sin coste. • Completa monitorización del vehículo, incluyendo el combustible disponible y la salud del motor y de la batería entre otros. • Identificación del conductor mediante una etiqueta NFC, impidiendo el arranque del vehículo sin esta identificación previa. • Compartición de datos de localización. • Visualización del seguimiento en tiempo real. • Reportes de actividades y rutas. • Generación de excepciones o alarmas. • Avisos auditivos dentro del vehículo. • Portal web para la consulta de los datos. Como podemos observar, es un servicio muy completo que aprovecha su moderno localizador GO7 por puerto OBD para dar información de monitorización muy completa y además ofrece identificación del conductor haciendo uso de la tecnología NFC. Sin embargo, no es posible consultar los precios sin ponernos en contacto ya que ofrecen diferentes servicios, más o menos completos, para adaptarse a un rango mayor de público. Como inconveniente sólo se podría destacar su disponibilidad geográfica, ya que únicamente da servicio a Estados Unidos y no conocemos el precio de este servicio. 2.4 Conclusiones Las soluciones analizadas previamente permiten una monitorización muy completa de una flota de vehículos además de la administración de la misma. Sin embargo, su complejidad desemboca en unos precios que un amplio sector empresarial no puede permitirse y preferirían renunciar a ciertas funcionalidades a cambio de un coste menor. Para finalizar, vamos a sintetizar las soluciones expuestas anteriormente en una tabla comparativa. 5 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos TABLA COMPARATIVA Coste inicial por vehículo Coste mensual por vehículo Disponibilidad en España Elementos identificados Movistar: Seguimiento CarClick 95,59€ 27,71€ Sí Vehículo Movistar: Seguimiento flotas 175,45€ 26,50€ Sí Vehículo Movistar: Seguimiento móviles 0€ 20,45€ Sí Conductor Verizon Desconocido 30$ Desconocido Vehículo Fleetistics Desconocido Desconocido No Vehículo y conductor 7 3 OBJETIVOS omo conclusión del análisis anterior, necesitamos una herramienta que cumpla dos condiciones: Precio asequible e identificación del vehículo y del conductor de manera conjunta en tiempo real. Para garantizar un precio reducido, debemos evitar el uso de localizadores GPS ya que presentan un coste elevado tanto de instalación como de mantenimiento en la mayoría de los casos. Por tanto, aprovecharemos los terminales móviles de nuestros conductores como dispositivos agentes. Los teléfonos móviles cada día poseen más potencia y gracias a la triangulación de antenas de red y su módulo GPS, ofrecen una gran precisión y agilidad a la hora de realizar tareas de seguimiento. Además, su crecimiento en la sociedad hace que un alto porcentaje de la población de este país posea un teléfono inteligente con potencia suficiente como para realizar este seguimiento por lo cual se podrían utilizar para este fin. Hasta aquí, se podría identificar al conductor y realizar un seguimiento del personal, sin embargo aún no cumplimos nuestra segunda condición, necesitamos identificar al vehículo. Para ello nuestra solución es instalar o reutilizar algún tipo de dispositivo que se comunique con el terminal del usuario; los dispositivos de manos libres se convertirán en nuestros identificadores del vehículo. Estos dispositivos poseen una dirección física única que nos permitirán identificar un vehículo y, una vez enlazado con el teléfono del conductor, podremos crear una acción de disparo del rastreo cuando se produzca la conexión entre la aplicación móvil y el dispositivo. Durante este rastreo, la aplicación móvil enviará una serie de datos a un servidor remoto que los recopilará y generará información útil para el administrador. Una vez finalizada la conducción, el teléfono móvil se desconectará del dispositivo Bluetooth y al producirse esta acción, se dará fin al seguimiento. La información generada se podrá consultar a través de un navegador web dándose a conocer datos relevantes que mejorarán la eficiencia y el aprovechamiento de estos vehículos. Entre esta información destacamos: • Posición en tiempo real de vehículos y conductores o última posición conocida en caso de que no estén en seguimiento en ese instante. • Duración, distancia, velocidad y consumo medio de los viajes de un usuario o un vehículo. • Visualización del recorrido de las distintas sesiones de conducción registradas en un mapa así como su marca horaria de inicio y final. • Comparativas gráficas entre un usuario, vehículo o ruta frente al resto. • Generación de informes en formato CSV, compatible con las hojas de cálculo más utilizadas. • Administración básica de conductores y vehículos, permitiéndose su registro, habilitación o deshabilitación. En definitiva, es posible llevar a cabo tareas de monitorización de este tipo sin invertir en nuevos elementos físicos al reutilizar dispositivos de manos libres y teléfonos móviles inteligentes. C 14 Requerimientos La redacción de contenido HTML es muy sencilla y sólo es necesario comprender dos tipos de componentes distintos, los elementos y los atributos. 4.4.1.1 Elementos Los elementos son la estructura básica de HTML y poseen dos propiedades; atributo y contenido. Su sintaxis es sencilla, encerrando el nombre de elemento y los atributos entre corchetes angulares del siguiente modo: <nombre_elemento atributo="valor"> CONTENIDO </nombre_elemento> Estos elementos se pueden incluir como contenido de otros elementos y, de este modo, podemos describir un documento web. <!DOCTYPE html> <html> <head> <title>Page Title</title> </head> <body> <h1>This is a Heading</h1> <p>This is a paragraph.</p> </body> </html> El fragmento anterior es un ejemplo de documento HTML. Como podemos notar, existe una etiqueta principal (DOCTYPE) que indica el tipo de documento y, a partir de ahí, ramificamos elementos para construir el documento. Dentro de la etiqueta HTML se distinguen siempre una cabecera (head) y un cuerpo (body). En la cabecera se añaden los metadatos de la página (el título, por ejemplo), y en el cuerpo el contenido propiamente dicho del documento. 4.4.1.2 Atributos Dentro de los elementos podemos especificar atributos, en la mayoría de los casos se trata de pares nombrevalor separados mediante la sintaxis atributo="valor". 4.4.2 PHP Acrónimo recursivo de PHP: Hypertext Preprocessor [10], es un lenguaje de código abierto muy popular orientado al desarrollo web y que puede ser utilizado conjuntamente con HTML. Por otro lado, PHP está centrado en la programación de scripts (interpretado) en el lado del servidor. De este modo, en el servidor se procesará un documento PHP y generará contenido HTML para el cliente y, a diferencia de JavaScript, el cliente no podrá conocer el código que ha generado este contenido estático. El corazón de nuestro servidor se basará en PHP y gestionará, entre otros, el acceso y edición a la base de datos, sesiones de navegación y la generación de gran parte del contenido dinámico. Los fragmentos escritos en PHP se encierran entre etiquetas <?php y ?> como podemos ver en el siguiente ejemplo: <!DOCTYPE html> 15 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos <html> <body> <?php echo "My first PHP script!"; ?> </body> </html> Las distintas funciones PHP se pueden consultar en la documentación de su página web. 4.4.3 JavaScript JavaScript es un lenguaje de programación interpretado orientado a objetos procedente del estándar ECMAScript. La particularidad de este lenguaje es que es fácilmente empotrarlo en contenido HTML para ofrecer comportamiento dinámico en el lado del cliente, aunque hay variantes que permiten su ejecución en el lado servidor. En nuestra aplicación se utilizará para validar formularios, usar la librería jQuery y, por último pero no menos importante, implementar mapas usando la API de Google. Los scripts de JavaScript se encierran entre las etiquetas <script> y </script> o bien se llaman haciendo uso de los atributos de algunos elementos, como podemos ver en el siguiente ejemplo: <!DOCTYPE html> <html> <body> <h1>My First JavaScript</h1> <button type="button" onclick="document.getElementById('demo').innerHTML = Date()"> Click me to display Date and Time.</button> <p id="demo"></p> </body> </html> En este ejemplo, al pulsar en el botón se modifica el contenido (innerHTML) del párrafo de debajo a la hora y fecha actual. 4.4.4 MySQL MySQL [11] es un sistema de gestión de bases de datos relacional, multihilo y multiusuario desarrollado como software libre. Su portabilidad y su sencilla implementación en servicios web ha sido determinante para su uso en nuestra aplicación. Este gestor ofrece interfaces de programación de aplicaciones que permiten a los distintos lenguajes de programación acceder a las bases de datos MySQL. En nuestro caso, los accesos se llevarán a cabo vía PHP. 4.5 Especificación de requisitos del sistema En este apartado se redactará la especificación de requisitos para la que desarrollaremos nuestra herramienta, 16 Requerimientos además, se han clasificado y etiquetado para futuras referencias [12]. 4.5.1 Requisitos generales RG001. La aplicación cliente deberá iniciar el seguimiento al detectar el inicio de una conexión Bluetooth. RG002. La aplicación cliente deberá finalizar el seguimiento al detectar el fin de la conexión Bluetooth que la inició. RG003. Existirá un portal web a través del cual los usuarios podrán realizar consultas sobre los datos recolectados. 4.5.2 Requisitos funcionales Los requisitos funcionales son aquellos que definen una función del software a desarrollar. A su vez, se han clasificado para permitir una mejor referencia a ellos. 4.5.2.1 Casos de uso CU001. El usuario podrá introducir su usuario y contraseña en la aplicación cliente. CU002. El usuario podrá introducir la URL del servidor en la aplicación cliente. CU003. El usuario podrá iniciar un seguimiento manualmente desde la aplicación cliente. En este seguimiento no se identificará a ningún vehículo y se definirá como un seguimiento manual. CU004. El usuario podrá detener un seguimiento manual desde la aplicación cliente. CU005. El usuario podrá identificarse para acceder a la página web. CU006. El usuario podrá consultar información acerca de los conductores en la página web. CU007. El usuario podrá consultar información acerca de los vehículos en la página web. CU008. El usuario podrá consultar información acerca de las rutas registradas en la página web. CU009. El usuario podrá dar de baja tanto a conductores como a vehículos en la página web. CU010. El usuario podrá dar de alta tanto a conductores como a vehículos en la página web. CU011. El usuario podrá generar informes CSV de la información que desee en la página web. CU012. La aplicación cliente podrá negociar la inclusión de nuevos datos a la base de datos del servidor. 4.5.2.2 Requisitos de información IN001. El mapa de la aplicación cliente mostrará la siguiente información: un indicador de inicio de la ruta, un indicador del final de la ruta y la ruta dibujada como una línea entre ambos puntos. IN002. La aplicación cliente mostrará un mensaje en todo momento informando de su estado. IN003. La aplicación cliente almacenará únicamente la información referente a las ubicaciones de la última ruta o ruta en curso si la hubiera. IN004. La aplicación cliente almacenará el usuario y la contraseña. IN005. La información almacenada en el servidor sobre los conductores será: Usuario, contraseña, estado (habilitado o deshabilitado), nombre y apellidos. IN006. La información almacenada en el servidor sobre los vehículos será: Matrícula, dirección física del dispositivo Bluetooth asociado (MAC), modelo, estado (habilitado o deshabilitado) y consumo medio cada 100 kilómetros. IN007. La información almacenada en el servidor sobre las sesiones será: Identificador de sesión, matrícula del vehículo, usuario del conductor y una lista de ubicaciones por las que ha pasado. 17 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos IN008. La información almacenada en el servidor sobre las diferentes ubicaciones de una sesión será: orden de llegada, latitud, longitud, hora y fecha del cliente, hora y fecha del servidor y posibles comentarios. IN009. A partir de la información almacenada en el servidor siguiendo los requisitos IN005, IN006, IN007 e IN008; se podrá generar más información disponible para la consulta de los usuarios como por ejemplo la velocidad. 4.5.2.3 Requisitos de conducta CO001. Antes de cada seguimiento, la aplicación debe identificar al usuario y al vehículo a través del servidor. CO002. El seguimiento debe realizarse en segundo plano. CO003. En caso de cierre inesperado de la aplicación, deberá restaurar sus servicios continuando con el seguimiento. CO004. La aplicación deberá impedir al usuario la detención manual de un seguimiento iniciado automáticamente. CO005. Se priorizará un seguimiento automático frente a uno manual. Es decir, si existe un seguimiento manual en curso y detectamos un dispositivo Bluetooth que pertenece a la lista de los vehículos que debemos rastrear, debemos finalizar el seguimiento manual e iniciar el seguimiento automático. CO006. Se impedirá el inicio manual si existe un seguimiento automático en curso. 4.5.2.4 Requisitos de diseño DI001. Se mostrará una interfaz principal en el cliente en la que aparecerá el mapa, un texto de estado y un botón de encendido y apagado. DI002. Se mostrará una interfaz secundaria de ajustes en el cliente en la que se podrá configurar los parámetros de la aplicación. DI003. Se ofrecerá un portal de bienvenida web en el que el usuario, además de poder identificarse para acceder al contenido, podrá ver noticias. DI004. En el contenido de la página web se distinguirán las siguientes partes: cabecera, menú, filtro de búsqueda, barra de acción y tabla de contenidos. DI005. En el menú de la página web se distinguirán los siguientes apartados: conductores, vehículos, rutas y administrar. DI006. En el filtro de búsqueda de la página web se permitirá decidir qué información mostrar en la tabla de contenidos. DI007. En la tabla de contenidos de la página web se mostrará información básica sobre los elementos y se permitirá acceder a una vista detallada de los mismos. DI008. En la barra de acción de la página web habrá botones para realizar acciones globales como por ejemplo generar informes. DI009. En la ficha detallada de la página web se mostrará toda la información del elemento en cuestión haciendo uso de mapas y gráficas. DI010. En el apartado administrar de la página web se permitirá dar de alta a nuevos conductores y vehículos. 4.5.3 Requisitos no funcionales Los requisitos no funcionales definen la calidad de la herramienta a implementar y especifica los criterios que pueden seguirse para comprobar la validez del sistema sin evaluar su comportamiento. 18 Requerimientos 4.5.3.1 Requisitos de fiabilidad FI001. La herramienta deberá impedir el seguimiento sin una fase de negociación satisfactoria. FI002. La aplicación cliente deberá ser persistente en caso de cierre abrupto. FI003. La aplicación cliente deberá conmutar de manera transparente su funcionamiento en primer y segundo plano. 4.5.3.2 Requisitos de usabilidad US001. La aplicación cliente mostrará la ubicación en tiempo real durante cualquier seguimiento, manual o automático o la última ruta realizada en caso contrario. US002. Durante un seguimiento, el usuario podrá ver un mapa en el cliente. US003. El usuario podrá decidir si mostrar o no el mapa en la aplicación cliente. US004. En todo momento el usuario podrá ver el estado de la aplicación cliente en un texto. 4.5.3.3 Requisitos de seguridad SE001. Se necesitará una clave de la API de Google para el uso de los mapas de Google4. SE001. El servidor deberá impedir que usuarios sin identificar accedan a la información almacenada. SE002. El servidor deberá impedir que se añadan datos a la base de datos sin una fase de negociación satisfactoria. 4.5.4 Restricciones técnicas RT001. La aplicación móvil será desarrollada para versiones de Android superior a la API 15. RT002. Se solicitará que la aplicación Servicios de Google se encuentre instalada en el terminal móvil. RT003. Se necesitará permiso a modificar o eliminar el contenido de la tarjeta SD del terminal móvil. RT004. Se necesitará permiso a la lectura de la ubicación GPS del terminal móvil. RT005. Se necesitará permiso a la lectura de una ubicación aproximada por triangulación de redes del terminal móvil. RT006. Se necesitará permiso de acceso íntegro a Internet del terminal móvil. RT007. Se necesitará permiso a visualizar el estado de la red del terminal móvil. RT008. Se necesitará permiso a consultar conexiones de Bluetooth del terminal móvil. RT009. Se necesitará permiso para leer la configuración de los permisos de Google del terminal móvil. 4!Sólo%para%su%compilación" 19 5 DISEÑO n este capítulo nos centraremos en la etapa de diseño del software a implementar, para ello nos apoyaremos en el lenguaje de modelado unificado (UML) [12] que nos aportará la abstracción necesaria para llevar a cabo esta tarea. Este diseño lo dividiremos en secciones; partiremos en primer lugar de una visión general; en segundo lugar, diseñaremos la comunicación entre la aplicación cliente y el servidor; en tercer lugar, se diseñará la aplicación cliente; y, por último, haremos lo mismo con el servidor. 5.1 Visión general Expliquemos el funcionamiento de la herramienta apoyándonos en un diagrama de actividad muy simplificado desde el punto de vista del cliente: Figura 5-1. Comportamiento básico del cliente Como podemos observar, todo comienza con una señal entrante o evento que nos informa de la conexión con un dispositivo Bluetooth previamente emparejado. A priori, no podemos saber si debemos rastrear el dispositivo al que nos hemos conectado y, por tanto, el cliente llevará a cabo una negociación con el servidor para conocer si este dispositivo está instalado en un vehículo de la empresa y si tenemos permiso para rastrearlo. Esta negociación inicial trata la identificación tanto del cliente como del vehículo. El protocolo e intercambio de mensajes utilizados se explicarán con más detalle en una sección dedicada a ello más adelante. En caso recibir una respuesta negativa, no debemos iniciar el seguimiento; en cambio, si la respuesta es positiva, se lleva a cabo el seguimiento hasta que se reciba un evento de desconexión del Bluetooth. Además, incluiremos en la aplicación del cliente un modo de activación manual para su uso en el caso de que se necesite seguimiento sin necesidad de vehículo o éste no disponga de un dispositivo Bluetooth asociado. En cuanto al servidor, alojaremos en él una base de datos para llevar a cabo el almacenamiento de los datos recibidos, además de desplegar una página web que nos ofrezca información generada a partir de los datos recolectados así como una página de administración del personal y la flota de vehículos. 5.2 Diseño de la comunicación En esta sección estudiaremos la comunicación que existe entre un cliente y el servidor y, para facilitar su comprensión se adjunta a continuación un diagrama de secuencia. E 20 Diseño Figura 5-2. Intercambio de mensajes entre cliente y servidor Puesto que en el diagrama de secuencia las acciones vienen numeradas, seguiremos el mismo plan de numeración a la hora de describir cada uno de estos mensajes. 1. Se trata del mensaje inicial, la aplicación cliente enviará el usuario y contraseña previamente configurados al servidor. 1.1. El servidor consulta este par de usuario y contraseña en la base de datos. 1.2. Se responde indicando que no existe en la base de datos esa combinación y finaliza la negociación. 1.3. Se responde indicando que existe dicha combinación y el usuario ya queda identificado. 1.3.1. La aplicación cliente envía la dirección física del dispositivo Bluetooth que ha provocado el inicio de la negociación 1.3.1.1. El servidor comprueba si esa dirección pertenece a uno de sus vehículos. 1.3.1.2. Responde negativamente y finaliza la negociación. 1.3.1.3. Responde con un identificador de la sesión que acaba de registrarse y el vehículo ya queda identificado. 1.3.1.3.1. Se envía una serie de datos5 de manera periódica al servidor. El envío de datos periódico cesará cuando la aplicación móvil decida finalizar el seguimiento, es decir, cuando se pierda la conexión con el dispositivo Bluetooth o se detenga manualmente. Como podemos observar, todos los mensajes siguen un comportamiento del tipo petición-respuesta que se hacen uso del protocolo HTTP de nivel de aplicación. En cuanto al tipo de petición HTTP, nos hemos decantado por mensajes de tipo POST debido a que su papel será interactuar con una base de datos al otro lado de la comunicación (servidor). 5!Identificador,de,sesión,,hora,del,sistema,,fecha,del,sistema,,latitud,y,longitud." 21 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Por otro lado, puesto que HTTP es un protocolo sin estado, utilizaremos sesiones PHP en el lado del servidor cuando sea necesario retener información como, por ejemplo, para comprobar asegurarnos de que el usuario que nos envía datos de seguimiento se ha identificado previamente. 5.3 Diseño del cliente Siguiendo nuestras intenciones, a continuación se analiza el diseño de la aplicación que actúa como cliente en la comunicación. Para ello, comenzaremos estudiando la interfaz gráfica que queremos implementar a partir de bocetos y pasaremos a analizar el modelo estructural y de comportamiento de las diferentes clases que constarán nuestra aplicación. 5.3.1 Interfaz gráfica de usuario Siguiendo los requisitos de diseño del capítulo anterior, se ha diseñado un boceto de la aplicación cliente. A la izquierda, el aspecto que tendría la aplicación principal y, a la derecha, el aspecto que tendría la actividad de ajustes. Figura 5-3. Boceto de la interfaz principal Figura 5-4. Boceto de la interfaz secundaria Además, esta interfaz gráfica satisface los casos de uso que se expusieron en la especificación de requisitos del sistema. 22 Diseño Figura 5-5. Diagrama de casos de uso del cliente 5.3.2 Resumen de clases Para llevar a cabo el desarrollo del cliente se han elaborado nueve clases que, conjuntamente, se encargarán de conseguir el comportamiento esperado. • Una actividad principal, llamada MainActivity. • Una actividad que permite modificar los ajustes, llamada SettingsActivity. • Un servicio orientado a la comunicación con el servidor, llamado ConnectionService. • Un servicio orientado al seguimiento y rastreo del dispositivo, llamado LocationService o LocationServiceCompatible si nuestro dispositivo no es compatible con el primero. • Una clase orientada a gestionar los eventos Bluetooth, llamada BluetoothManager. • Una clase orientada a gestionar el mapa de la actividad principal, llamada MapManager. • Una clase que nos servirá como interfaz para acceder al estado y a los datos de la aplicación, llamada PreferencesManager. • Y por último, una clase que, cuando cambie el estado de la aplicación, se encargue de modificar la interfaz de la actividad principal. Esta última se llama PreferencesListener. A continuación se muestran los modelos estructurales y de comportamiento de estas clases de manera individual aunque se puede consultar una visión general de la estructura del cliente en los anexos6. 5.3.3 Actividades 5.3.3.1 Actividad principal La actividad principal será la pantalla principal de la aplicación y será la encargada de proporcionar al usuario la información del seguimiento. 6!Consultar*diagrama*de*clases*completo*en*los*anexos" 23 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos 5.3.3.1.1 Modelo estructural de la clase Para iniciar la descripción de esta actividad, podemos apoyarnos en un pequeño diagrama de clases que nos mostrará las relaciones de esta clase con el resto. Figura 5-6. Diagrama de clases parcial (MainActivity) Como podemos observar, nuestra actividad principal tiene relación con cuatro clases diferentes de nuestra aplicación, analicemos cada una de estas relaciones para comprender mejor su funcionamiento. • Relación con la clase PreferencesManager. La actividad podrá instanciar y usar un objeto de esta clase para comprobar el estado de cada componente de la aplicación en todo momento y, además, podrá registrarse como objetivo de las notificaciones de una clase PreferencesListener que explicaremos a continuación. • Relación con la clase PreferencesListener. La instancia de esta clase creada por PreferencesManager escuchará los cambios en las preferencias (SharedPreferences) de nuestra aplicación y, en función de lo que haya cambiado, irá actualizando la interfaz de nuestra actividad principal en tiempo real. • Relación con la clase MapManager. La actividad principal tendrá un objeto de este tipo encargado de gestionar el mapa de manera íntegra, se explicará más adelante en su apartado correspondiente. • Relación con la clase ConnectionService. Esta es la relación más compleja e importante de todas. Ésta clase actuará como un servicio que regulará la comunicación con el servidor e iniciará el seguimiento cuando la fase de negociación sea satisfactoria, nuestra actividad principal podrá crear el servicio e iniciarlo y detenerlo siempre que se cumplan los requisitos de prioridad. 5.3.3.1.2 Modelo de comportamiento de la clase Una vez conocemos los componentes de nuestra actividad, intentemos integrar todos los elementos y explicar su funcionamiento a partir de un simplificado diagrama de actividad. 30 Diseño 3. El servicio se queda a la espera de eventos. Los distintos eventos que pueden interrumpir este estado de espera son los siguientes: • Conexión con la API de Google, a través del método onConnected y propiciará la llamada al método startTrack que inicia el rastreo. • Errores en la conexión, a través de los métodos onConnectionSuspended y onConnectionFailed, que lanzará la reconexión con la API. • Nueva ubicación obtenida, gestionada a través del método onLocationChanged y que enviará datos al servidor utilizando el método sendData del servicio ConnectionService. • Destrucción del servicio, a través del método onDestroy se finalizará el rastreo (stopTrack) y el servicio será destruido. 5.3.5 Resto de clases 5.3.5.1 Clase orientada a gestionar los eventos Bluetooth Esta clase resulta de gran importancia en nuestra aplicación ya que sin ella no podríamos detectar eventos Bluetooth y, por tanto, no existiría el seguimiento automático. En definitiva, es la clase encargada de capturar los distintos eventos del sistema y extiende de la clase BroadcastReceiver, es decir, es un receptor de mensajes de difusión. 5.3.5.1.1 Modelo estructural de la clase Figura 5-14. Diagrama de clases parcial (BluetoothManager) Como podemos observar, BluetoothManager sólo comparte relación con dos clases. • Con PreferencesManager, para leer y editar los datos y el estado de la aplicación. • Con ConnectionService, pudiendo iniciar el servicio (seguimiento automático) y finalizarlo. En cuanto a las variables y métodos de la clase, observamos que no aporta nada distinto a un BroadcastReceiver genérico, salvo que se ha particularizado para detectar eventos Bluetooth. 31 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos 5.3.5.1.2 Modelo de comportamiento de la clase El comportamiento de esta clase es muy básico; cada vez que el sistema genere un mensaje de difusión de los que debemos capturar, se ejecutará el constructor en primer lugar y a continuación se llamará al método onReceive. En este método, a partir de sus parámetros, se averiguará el evento que lo ha excitado y a partir de esta información, iniciará y finalizará el servicio de comunicaciones (ConnectionService) del mismo modo que cuando se hacía manualmente. 5.3.5.2 Clase orientada a gestionar el mapa Esta clase se llama MapManager e implementa la clase OnMapReadyCallback. Su función es instanciar el mapa y gestionar tanto su estado como la información que aparece (ruta recorrida, posición actual, etcétera). Por otro lado, presenta métodos que ayudan a gestionar los distintos estados de la actividad principal (iniciada, pausada, parada y destruida). 5.3.5.2.1 Modelo estructural de la clase Figura 5-15. Diagrama de clases parcial (MapManager) Como podemos observar, existe relación con tres elementos distintos. • Un objeto de la clase MapManager podrá ser instanciado y usado por la actividad principal (MainActivity) para que gestione su mapa por él. • Utilizará un objeto de la clase PreferencesManager facilitado por la actividad principal para poder leer la información de la ruta actual o la última ruta realizada así como el estado de la aplicación. • En el mapa se verá la posición en tiempo real con una frecuencia de muestreo mayor al del rastreo en segundo plano, por tanto, si la aplicación está en primer plano y encontramos una ubicación se la enviaremos a los servicios de localización (LocationService o LocationServiceCompatible) para que la gestione como se debe. 32 Diseño 5.3.5.3 Clase orientada a gestionar las preferencias Esta clase tan utilizada y mencionada a lo largo de la memoria actúa como barrera entre los datos y el resto de componentes de la aplicación. La clase se llama PreferencesManager y, en definitiva, posee un conjunto de métodos GET y SET que nos permite leer y modificar tanto el estado como los datos de la aplicación a través de una instancia de la clase SharedPreferences, que es un proveedor de contenidos de Android. Puesto que la estructura y comportamiento son realmente básicos, la manera de explicar esta clase se basará en mostrar los datos que este elemento es capaz de administrar. 5.3.5.3.1 Operaciones que permite la clase A continuación se exponen las diferentes operaciones que permite esta clase, junto con una breve descripción. Estado de la aplicación isHttpSession Comprueba si el servicio de comunicaciones está activo isGpsSession Comprueba si alguno de los servicios de rastreo está activo isAutoMode Comprueba si la sesión actual es automática o manual setAutoMode Establece el tipo de la sesión actual Datos de la aplicación setUrl Establece una nueva URL del servidor getUrl Obtiene la URL del servidor configurada setUser Establece un nuevo usuario getUser Obtiene el usuario configurado setPass Establece una nueva contraseña getPass Obtiene la contraseña configurada setMac Establece una nueva MAC de ruta actual getMac Obtiene la MAC ligada a la ruta actual Aspecto de la aplicación setShowMap Define que el mapa sea visible o no lo sea isShowMap Consulta si el mapa debe ser visible o no setStatus Modifica el texto que define el estado actual de la aplicación getRouteColor Obtiene el color del cual se debe pintar la ruta en el mapa setRouteColor Establece un nuevo color del cual se debe pintar la ruta en el mapa Ruta almacenada 33 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos addToLastRoute Añade una nueva ubicación a la última ruta (o ruta actual) getLastRoute Obtiene la última ruta almacenada (o ruta actual) clearLastRouteInfo Limpia la información acerca de la última ruta almacenada Especial setPreferencesListener Declara un Listener que modificará la interfaz de la actividad principal cuando cambie el mensaje de estado. 5.3.5.4 Clase orientada a actualizar la interfaz de la actividad principal La clase PreferencesListener implementa la clase OnSharedPreferenceChangeListener y se encargará de ejecutar el método updateStatus de la actividad principal (si está en ejecución) para actualizar su texto de estado cada vez que se detecte un cambio en el estado de la aplicación. La detección de estos cambios se conseguirá ya que escuchará los cambios de las SharedPreferences. Figura 5-16. Diagrama de clases parcial (PreferencesListener) Como podemos observar en la figura anterior, la actividad principal instancia un objeto de la clase PreferencesManager y luego lo utiliza para registrarse como receptor de la clase PreferencesListener. 5.4 Diseño del servidor Para finalizar con la fase de diseño de nuestra herramienta, en esta sección se llevará a cabo esta tarea para el servidor. Para ello comenzaremos con el diseño de la página web (estética), pasaremos por el diseño de la base de datos estudiando su modelo entidad-relación y, para finalizar, estudiaremos los algoritmos que se aplican para atender las peticiones del cliente. 5.4.1 Interfaz gráfica de la página web Del mismo modo que se hizo con el cliente, para el servidor también se ha diseñado un boceto del aspecto que queremos que tenga nuestra página web para que cumpla los requisitos de diseño e información. En primer lugar, se adjunta un boceto de una vista general de contenidos, dependiente de la sección que 34 Diseño estemos visualizando (conductores, vehículos, rutas o administrar). Figura 5-17. Boceto de la vista general del navegador web Y en segundo lugar, observamos la estética de una ficha detallada que aparecería al interactuar con algún elemento de la tabla anterior. Figura 5-18. Boceto de la vista detallada en el navegador web 35 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Partiremos de este diseño a la hora de implementar la página web del servidor. 5.4.2 Base de datos Para poder llevar a cabo la recolección de los datos, es necesario crear y preparar una base de datos que almacene de manera correcta y eficiente la información. Haciendo uso de MySQL y consultando su documentación, se ha construido8 la siguiente estructura de la base de datos bajo el nombre de TFG. 5.4.2.1 Modelo entidad-relación A continuación se muestra el modelo entidad-relación de la base de datos que se va a implementar en nuestro servidor. Figura 5-19. Modelo ER de la base de datos 5.4.2.2 Tabla USER Esta tabla nos permitirá almacenar información acerca de los conductores. Sus atributos son los siguientes: • user, que almacenará una cadena de caracteres que será el usuario del conductor y además será la clave primaria de nuestra tabla. • pass, que almacenará la contraseña de nuestro usuario. • status, que será un booleano que indique si nuestro usuario está habilitado o deshabilitado. • name, que representará el nombre del usuario como una cadena de caracteres. • surname, que mostrará los apellidos de nuestro usuario. 8!Instrucciones+en+los+anexos" 36 Diseño 5.4.2.3 Tabla VEHICLE La tabla VEHICLE nos permitirá almacenar información sobre los vehículos, sus columnas se explican a continuación: • id_vehicle será el identificador del vehículo y vendrá dado por su matrícula (cadena de caracteres). Además será la clave primaria de la tabla. • mac será el campo que almacene la dirección física del Bluetooth asociado a este vehículo. • model se referirá al modelo u otra información adicional que se quiera especificar del vehículo en cuestión. • status recogerá el estado (booleano) del vehículo, es decir, si está habilitado o deshabilitado. • energy almacenará un entero que representará el consumo medio del vehículo cada 100 kilómetros (valor anunciado por el fabricante). 5.4.2.4 Tabla SESSION La tabla SESSION almacenará información general sobre las sesiones de seguimiento, sus columnas se explican a continuación: • id_session será el identificador de la sesión y será un entero autoincrementado, por lo tanto será la clave primaria de nuestra tabla SESSION. • id_vehicle será una referencia a la clave primaria de la tabla VEHICLE, por tanto, la utilizaremos como clave externa. • user, del mismo modo que con la columna anterior, será una clave externa que haga referencia a la clave primaria de la tabla USER. El objetivo de esta tabla es establecer una relación entre los conductores y los vehículos construyendo una sesión de seguimiento. 5.4.2.5 Tabla SESSION_DET La tabla SESSION_DET almacenará información de seguimiento, sus campos son los siguientes: • id_session clave externa que hará referencia al identificador de una sesión. Formará parte de la clave primaria. • arrive_order será el orden de llegada de una ubicación dentro de una sesión. Será autoincremental y completará la clave primaria. • lat será una cadena que represente una latitud. • lon será una cadena que represente una longitud, junto con la latitud determinan una ubicación en un momento dado. • time_c, almacena la hora del cliente al enviar los datos. • date_c, almacena la fecha del cliente al enviar los datos. • time_s, almacena la hora del servidor al recibir los datos. • date_s, almacena la fecha del servidor al recibir los datos. • comment, aunque no es utilizado en esta versión de la herramienta, podría servir en un futuro para anotar incidencias durante la sesión u otros comentarios de interés. Cada fila en esta tabla identificará una ubicación en un momento dado de una sesión y, a partir de dicha sesión, podremos conocer el usuario y el vehículo involucrados. 37 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos 5.4.3 Atención de peticiones Las peticiones del cliente se atenderán a través de tres ficheros PHP que representarán las diferentes fases en la comunicación entre ellos. 5.4.3.1 Identificación del usuario Se lleva a cabo a través del fichero identify.php y su comportamiento es el siguiente: Figura 5-20. Fase I en el lado del servidor Como podemos observar en el diagrama, sólo se asigna una sesión PHP en caso de que exista el par usuariocontraseña en la base de datos. Para hacer esta comprobación se realiza la siguiente consulta SQL. SELECT * FROM USER WHERE user = 'usuario' AND pass = 'contraseña' AND status = true; Si la clausula anterior obtiene algún resultado se comprueba que el usuario exista, la contraseña sea correcta y además esté habilitado. 5.4.3.2 Identificación del vehículo La identificación del vehículo sería la segunda fase de la negociación y consistiría en comprobar si la MAC detectada es válida (pertenece a la base de datos) y, en caso afirmativo, crear una entrada en la tabla SESSION. Esto se lleva a cabo en el fichero validatemac.php. 38 Diseño Figura 5-21. Fase II en el lado del servidor La entrada que se crea en la tabla SESSION de la base de datos establecerá una relación entre un conductor y un vehículo. Este registro se realiza mediante la siguiente cláusula SQL: INSERT INTO SESSION (id_session, id_vehicle, user) VALUES (NULL, 'matricula', 'usuario'); Asignando el valor NULL al campo id_session hacemos uso de la capacidad auto-incremental del mismo y a continuación tendremos que obtener este valor para devolverlo al cliente. SELECT LAST_INSERT_ID() FROM SESSION; Esta cláusula devuelve el último valor auto-incrementado de la conexión con la base de datos y será la referencia que el cliente tenga con su sesión de seguimiento. 5.4.3.3 Recepción de datos En cuanto a la recepción y almacenamiento de datos, el diagrama que contempla su comportamiento es el siguiente: 39 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Figura 5-22. Recepción de datos en el lado del servidor Estas posiciones se registran mediante la siguiente cláusula SQL: INSERT INTO SESSION_DET (id_session, arrive_order, lat, lon, time_c, date_c, time_s, date_s) VALUES ('idsesion', 'ordenllegada', 'latitud', 'longitud', 'horacliente', 'fechacliente', 'horaservidor', 'fechaservidor'); 46 Implementación stopConnectionService. public void onClickSettingsButton (View view); Este método es el Listener del botón de ajustes, es decir, se ejecutará cada vez que pulsemos dicho botón y se encargará de lanzar la actividad de ajustes. public void updateStatus(String status); Este método es muy simple, se encargará de actualizar el texto de estado de la actividad a partir de una cadena enviada como parámetro. public void updateUi(); Este método se encargará de actualizar todos los elementos de la interfaz salvo el texto de estado. Para una mejor comprensión de su funcionamiento, se adjunta a continuación un diagrama de actividad: Figura 6-4. Diagrama de Actividad del método updateUi Como podemos ver, lo primero que comprobamos es si hay una sesión en curso, es decir, si hay alguna comunicación iniciada con el servidor y, en función de ello, actualizamos los valores del botón y la barra de progreso. 47 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos En caso de que haya una sesión en curso comprobamos si la sesión se ha iniciado automáticamente y dependiendo del resultado, permitimos que se pueda detener la sesión de manera manual o no. Para finalizar, comprobamos si se ha creado la vista del mapa para evitar excepciones y luego comprobamos si hay un rastreo en curso, en caso afirmativo activamos la actualización en tiempo real del mapa. De este modo conseguimos un método que actualiza la interfaz bajo demanda, lo llamaremos cada vez que se produzca un cambio del estado de la aplicación sin enviarle datos de ningún tipo ya que este método evalúa el estado automáticamente. private void startMap(Bundle savedInstanceState); Este método se encarga de comprobar si el usuario quiere mostrar un mapa o no y, en caso de que sí lo desee, inicializar tanto la vista de mapa como el objeto de la clase MapManager. Esta instancia nos servirá de ventana de comunicación con el mapa y se explicará más adelante. private void startConnectionService(); Sólo en caso de que no haya una sesión en curso, iniciará el servicio adecuado haciendo uso del Intent previamente definido en el método onCreate. Antes de hacerlo se encargará de eliminar toda información de la última sesión así como todo aquello que estuviera dibujado anteriormente en el mapa. private void stopConnectionService(); En este método se comprobará si hay alguna sesión que finalizar y lo hará si es necesario. public void loadPreferences(); Al llamar a este método se inicializa la variable mPreferencesManager como una instancia de la clase PreferencesManager. A través de esta instancia podremos acceder a un conjunto de métodos que nos facilitará la lectura y escritura de datos así como la consulta del estado de la aplicación (qué servicios se encuentran activos, etc.). protected void onResume(); protected void onPause(); protected void onDestroy(); public void onLowMemory(); En estos cuatro métodos se sobrescribe el método original de la clase Activity para gestionar los elementos en memoria cuando la actividad pasa a sus distintos estados. Ha resultado realmente útil para poder tener el control en memoria del mapa, por ejemplo. 6.1.2.2 SettingsActivity Esta actividad se alojará en el fichero SettingsActivity.java y también extenderá la clase Activity de Android. 6.1.2.2.1 Variables En este apartado vamos a comentar las variables que utilizaremos en esta clase. private TextView mEditUser; private TextView mEditPass; private TextView mEditUrl; private Switch mMapToggle; Estos atributos se utilizarán como referencia a los diferentes elementos que presenta la interfaz de esta actividad. private PreferencesManager mPreferencesManager; Esta variable de la clase PreferencesManager nos permitirá leer y modificar las entradas de las preferencias de la aplicación tales como el usuario, la contraseña, la dirección del servidor y la decisión del usuario sobre mostrar el mapa o no hacerlo. 48 Implementación 6.1.2.2.2 Métodos A continuación vamos a resumir el funcionamiento de cada uno de los métodos de esta clase. protected void onCreate(Bundle savedInstanceState); Sobrescritura del método de la clase Activity. Iniciará la variable mPreferencesManager y llamará al método setAll. private void setAll(); Declarará cada elemento de la interfaz y le dará el valor apropiado. Básicamente se encarga de actualizar la interfaz cuando algún valor haya cambiado o al inicio de la actividad. public void onClickCredentialsButton(View view); Se llamará cuando el usuario pulse el botón de edición de los credenciales. En primer lugar preparará y mostrará un objeto de la clase AlertDialog de forma que tenga el siguiente aspecto: Figura 6-5. Solicitud al usuario de nuevas credenciales Y tras la interacción del usuario, almacenaremos los nuevos valores del usuario y contraseña haciendo uso de la clase PreferencesManager. public void onClickServerButton(View view); Su funcionamiento es análogo al del método anterior, cambiando únicamente el aspecto del AlertDialog. Adicionalmente, comprobará si se trata de una URL o dirección IP válida. 49 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Figura 6-6. Solicitud al usuario de nueva URL del servidor public void onClickMapToggle(View view); Es el método que se encargará de escuchar las pulsaciones al botón de estado encargado de ocultar y mostrar el mapa. Tras cada pulsación reiniciará la aplicación, aunque los servicios (y en consecuencia, el seguimiento) no se verá afectado. Como podemos observar, en esta actividad no hemos sobrescrito todos los métodos de una clase de tipo Activity ya que no tenemos nada que aportar a estos elementos. 6.1.3 Servicios En esta sección se analizarán los servicios implementados, es decir, aquellos componentes preparados para ejecutar tareas largas en segundo plano. 6.1.3.1 ConnectionService Este servicio se encontrará codificado en el fichero ConnectionService.java y su desarrollo lo explicaremos siguiendo la misma estructura que en las actividades de los apartados anteriores. 6.1.3.1.1 Variables En esta sección estudiaremos las distintas variables que contiene nuestra clase con la finalidad de conocer su objetivo. private static ConnectionService mInstance; Se trata de una variable estática privada que nos permite definir un modelo Singleton en nuestra clase. Al ejecutarse el constructor se fijará esta variable impidiendo que existan múltiples instancias de esta clase. private HttpClient mHttpClient; private HttpResponse mHttpResponse; private HttpPost mHttpPost; 50 Implementación Estas tres variables privadas son utilizadas para llevar a cabo la comunicación por HTTP con el servidor. • HttpClient es una clase que gestiona cookies, autenticación, conexión y otros detalles de una comunicación vía HTTP. • HttpResponse nos permitirá almacenar la respuesta de una petición HTTP previa. • HttpPost servirá para encapsular un mensaje en una petición de tipo Post para su envío posterior a través de HTTP. Se podrían haber declarado localmente en cada uno de los métodos utilizados, aunque se ha preferido hacerlo de este modo para simplificar el desarrollo de los métodos. private PreferencesManager mPreferencesManager; Como en las clases anteriores, un objeto de la clase PreferencesManager nos permitirá acceder a la información y al estado de la aplicación, así como modificarlos. private Integer mSession; private String mUrl; private String mUser; private String mPassword; private String mMac; Las variables anteriores nos servirán para almacenar la información acerca de la sesión actual, evitando que consultemos las SharedPreferences de la aplicación y, en definitiva, mejorando su rendimiento. Inicialmente mSession no tendrá valor sino que se inicializará cuando se complete la fase de validación de la dirección MAC. private final static int TIME_BETWEEN_RETRY = 10000; private final static int TIMEOUT_CONNECTION = 5000; private final static int TIMEOUT_SOCKET = 3000; Estas últimas variables definen algunos tiempos en milisegundos para las conexiones HTTP. • TIME_BETWEEN_RETRY, como su propio nombre indica, nos muestra el tiempo entre reintentos. Se utilizará este valor como tiempo de reintento cuando la identificación o el envío de MAC haya fracasado. • TIMEOUT_CONNECTION se utilizará como tiempo de expiración de la conexión HTTP. • TIMEOUT_SOCKET se utilizará como tiempo de expiración del socket. Los dos últimos se utilizarán a la hora de inicializar la variable mHttpClient. 6.1.3.1.2 Métodos A continuación se expondrán los métodos que contiene este servicio junto con una breve descripción de su comportamiento. public ConnectionService(); Es el constructor por defecto de la clase, en él inicializamos las variables mHttpClient y mInstance con el fin de conseguir una clase Singleton. Este método se ejecuta únicamente cuando se crea el servicio. public int onStartCommand(Intent intent, int flags, int startId); Si recordamos el ciclo de vida de un servicio, este método se ejecuta cada vez que lo llamamos a través del método startService. En este método se realizan una serie de tareas haciendo uso de la clase AsyncTask. A través de esta clase, podemos ejecutar métodos en un hilo diferente al principal y esto es necesario para poder realizar una comunicación HTTP. Por tanto, en este método, se creará un objeto de la clase AsyncTask que llame en segundo plano al método startFlow que llevará a cabo la identificación y el envío de la MAC al servidor, al finalizar este método y 51 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos dependiendo de su resultado se llamará o no al método startTrack que se encargará de iniciar el seguimiento, es decir, iniciar el servicio encargado de ello. public void onDestroy(); Método sobrescrito de la clase Service, en él se llamará al método endFlow encargado de cerrar conexiones y finalizar el seguimiento. private boolean startFlow(); En este método se llevarán a cabo las siguientes tareas: 1. Inicializamos el objeto mPreferencesManager. 2. Inicializamos las variables mUrl, mUser, mPassword haciendo uso del objeto mPreferencesManager. 3. Comprobamos si queremos un seguimiento iniciado automática o manualmente y; en caso de que sea automática, inicializamos la variable mMac al valor de la MAC; en caso de que sea manual, le damos el valor NoMac12. 4. Actualizamos el texto de estado de la interfaz de usuario para que muestre el mensaje adecuado. 5. Gestionamos el intercambio de mensajes del siguiente modo: if (logIn()) { if (sendMac() && mSession != 0) { status = true; } } 6. Devolvemos el valor de la variable status. Valdrá true si la negociación se ha completado satisfactoriamente y false en caso contrario. Por tanto, se podría decir que es el método encargado de llamar a las distintas fases de la negociación así como de iniciar el seguimiento cuando sea necesario. public void startTrack(); Es el método encargado de iniciar el seguimiento. Puesto que hay dos servicios distintos que pueden llevar a cabo esta tarea se comprueba si la aplicación Servicios de Google Play se encuentra disponible. En caso positivo se inicia el servicio LocationService; en caso negativo se utiliza el servicio LocationServiceCompatible. Por otro lado, antes lanzar el servicio debemos comprobar si ya existe alguna instancia anterior (fruto de errores en la aplicación) y finalizarla. public boolean logIn(); En este método se creará una petición HTTP Post que identificará al usuario en el servidor. Además se gestionarán los posibles errores que puedan surgir tanto de comunicación como de identificación negativa. La creación de una petición de este tipo se realiza del siguiente modo: En primer lugar iniciamos el objeto mHttpPost especificando la URL del fichero PHP que va a gestionar la petición. mHttpPost = new HttpPost(mUrl + "/identify.php"); En segundo lugar, creamos los pares de datos que se enviarán en el cuerpo del mensaje. List<NameValuePair> nameValuePairList = new ArrayList<>(2); nameValuePairList.add(new BasicNameValuePair("user", mUser)); nameValuePairList.add(new BasicNameValuePair("password", mPassword)); 12!Valor&especial&que&el&servidor&interpretará&como&sesión&iniciada&manualmente." 52 Implementación mHttpPost.setEntity(new UrlEncodedFormEntity(nameValuePairList)); Por último, puesto que HTTP es un protocolo que sigue un modelo de petición y respuesta, debemos consumir la respuesta recibida. mHttpResponse = mHttpClient.execute(mHttpPost); A partir de esta respuesta, podremos conocer si la identificación es correcta (recibimos OK) o incorrecta (recibimos algo distinto). Si la petición devuelve alguna excepción significa que ha habido algún error en el envío y debemos reenviar el mensaje, es decir, esperar el tiempo TIME_BETWEEN_RETRY y volver a ejecutar el método logIn. Estos reintentos cesarán cuando el seguimiento ya no sea necesario (se ha perdido conexión con el dispositivo Bluetooth o se ha finalizado manualmente). public boolean sendMac(); El funcionamiento es análogo al del método logIn salvo que la información enviada y recibida son diferentes. El único dato enviado en este mensaje es la MAC del dispositivo Bluetooth, no es necesario enviar más información ya que la variable mHttpClient contendrá una Cookie con la sesión PHP activa, es decir, el servidor aceptará el mensaje si ya existe una sesión PHP ligada a esta conexión y esta sesión PHP sólo existirá si se ha identificado correctamente. Como respuesta obtendremos un número que identificará a la sesión en curso si la MAC es válida o un mensaje vacío si no lo es. Del mismo modo se reenviará el mensaje si hay errores en la comunicación. public void sendData(final String latitud, final String longitud); Su funcionamiento es similar a los anteriores sólo que en este caso la llamada al método no se realizará desde este servicio, sino desde el servicio de seguimiento como se explicará más adelante. La información enviada en esta petición es el identificador de la sesión, la latitud, la longitud, la hora y la fecha. public void endFlow(); Se llamará a este método cuando queramos finalizar el seguimiento; es decir, cuando haya una negociación fallida o se finalice el seguimiento de manera manual o automática. public static ConnectionService getHTTPInstance(); Es un método estático que devolverá la instancia única del servicio en nuestra aplicación, se utilizará para saber si existe alguna instancia (es distinto de null) o poder utilizar el método sendData (por parte del servicio LocationService) public IBinder onBind(Intent intent); Método vacío que es necesario sobrescribir, es el análogo al método onStartCommand aunque para llamadas ligadas al servicio mediante el método bindService. Como no hacemos nunca una llamada ligada al servicio no es necesario desarrollar su contenido. 6.1.3.2 LocationService Esta clase se encuentra desarrollada en el fichero LocationService.java, extiende la clase Service e implementa las clases LocationListener, ConnectionCallbacks y OnConnectionFailedListener. Mientras que la primera se utiliza para que reciba notificaciones acerca de la ubicación del dispositivo, las dos últimas encuentran su uso en la comunicación con la API de Google. 6.1.3.2.1 Variables A continuación vamos a explicar las distintas variables que maneja este servicio. 53 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos private static LocationService mInstance; En ella almacenaremos una instancia única de este servicio, es decir, será una clase Singleton. public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 8000; public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; Estas variables nos permitirán definir el tiempo deseado entre actualizaciones de la ubicación (8 segundos) y el tiempo mínimo (4 segundos). De estos valores dependerá mucho el consumo de batería y la precisión de la aplicación. private GoogleApiClient mGoogleApiClient; Esta variable actuará como cliente de la API de Google, es necesario para podernos suscribir a cambios en la ubicación del dispositivo. private LocationRequest mLocationRequest; Se trata de una variable que modelará el tipo de petición que le haremos a la API de Google, utilizaremos los tiempos anteriormente definidos para su inicialización. private Location mCurrentLocation; En esta variable almacenaremos la última localización conocida del dispositivo. Además, la clase Location nos permite conocer su procedencia, velocidad, etcétera que, aunque no utilicemos en esta versión de la aplicación, puede resultarnos útil en un futuro. private Boolean mRequestingLocationUpdates; Esta variable actúa como bandera a la hora de iniciar o no el rastreo. private PreferencesManager preferencesManager; Del mismo modo que el resto de clases, aquí también instanciamos un objeto de tipo PreferencesManager para acceder a los datos de la aplicación. 6.1.3.2.2 Métodos En este apartado vamos a analizar con algo más de detalle los distintos métodos de este servicio. public void onCreate(); Método que se ejecuta al crear el servicio por primera vez. En él, inicializamos la variable mInstance para establecer un patrón Singleton. public int onStartCommand(Intent intent, int flags, int startId); Se ejecuta cada vez que se inicia el servicio con el método startService. Llama al método buildGoogleApiClient e inicializa la variable mRequestingLocationUpdates al valor false. protected synchronized void buildGoogleApiClient(); Inicializa la variable mGoogleApiClient haciendo uso del método estático GoogleApiClient.Builder. A continuación, llama al método createLocationRequest y tras ello, solicita la conexión del cliente de la API de Google. protected void createLocationRequest(); Inicializa y prepara la variable mLocationRequest para que solicite actualizaciones con un intervalo deseado de UPDATE_INTERVAL_IN_MILLISECONDS milisegundos, intervalo mínimo de FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS milisegundos y alta precisión. public void startTrack(); Llama al método startLocationUpdates. public void stopTrack(); 54 Implementación Llama al método stopLocationUpdates. protected void startLocationUpdates(); Solicita actualizaciones de ubicación según los atributos preparados anteriormente. protected void stopLocationUpdates(); Finaliza las actualizaciones y, en definitiva, el seguimiento. public void onConnected(Bundle connectionHint); public void onConnectionSuspended(int cause); public void onConnectionFailed(ConnectionResult result); Son métodos sobrescritos que son llamados en los distintos estados de la conexión con la API de Google. En el primero, onConnected, se inicia el seguimiento (startTrack) ya que se ha conectado satisfactoriamente. En los dos últimos, se reintenta la conexión. public void onLocationChanged(final Location location); Método que se ejecuta cada vez que se detecta un cambio en la ubicación. En él, mediante el uso de la clase AsyncTask, llamamos al método sendData del siguiente modo: ConnectionService.getHTTPInstance().sendData(String.valueOf(location.getLatitude()), String.valueOf(location.getLongitude())); Sin olvidarnos de capturar todas las excepciones que se puedan producir. public static LocationService getGPSInstance(); Devuelve la instancia del servicio, es decir, la variable mInstance. public IBinder onBind(Intent intent); Método vacío que es necesario sobrescribir, es el análogo al método onStartCommand aunque para llamadas ligadas al servicio mediante el método bindService. Como no hacemos nunca una llamada ligada al servicio no es necesario desarrollar su contenido. public void onDestroy(); Método sobrescrito de la clase Service, en él se llamará al método stopTrack encargado de finalizar el seguimiento. 6.1.4 Resto de clases Aunque no sean actividades o servicios, hemos implementado algunas clases que han resultado claves en la implementación del cliente. Estas clases no vamos a analizarlas con tanta profundidad ya que su complejidad es reducida. 6.1.4.1 BluetoothManager Esta clase extiende la clase BroadcastReceiver, es decir, es un receptor de mensajes de difusión. Los mensajes de difusión que vamos a detectar son los siguientes: • Establecimiento de una conexión Bluetooth con algún dispositivo. android.bluetooth.device.action.ACL_CONNECTED • Fin de una conexión Bluetooth ya establecida. android.bluetooth.device.action.ACL_DISCONNECTED • Solicitud de desconexión de una conexión Bluetooth. 55 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED Estos eventos se detectarán a través del método onReceive de la clase que, además, recibirá como parámetro un objeto de tipo Intent del cual podremos extraer información acerca del dispositivo que ha generado la acción. BluetoothDevice mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); A partir de ese objeto, podremos leer por ejemplo la dirección física: mDevice.getAddress() Y el nombre del dispositivo: mDevice.getName() Gracias a esto, podremos gestionar correctamente el inicio y fin de los seguimientos automáticos. Para declarar este componente de Android, igual que las actividades y los servicios, se ha tenido que añadir información al fichero AndroidManifest.xml, especificando además los eventos que queremos recibir. <receiver android:name=".BluetoothManager" android:enabled="true" android:exported="true" > <intent-filter> <action android:name="android.bluetooth.device.action.ACL_CONNECTED" /> <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" /> <action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"/> </intent-filter> </receiver> 6.1.4.2 MapManager Si recordamos la actividad principal, existía un método llamado startMap y en él se ejecutaba lo siguiente: if (mPreferencesManager.isShowMap()) { // Inicializamos la vista que contiene al mapa mMapView = (MapView) findViewById(R.id.mapView); mMapView.onCreate(savedInstanceState); MapsInitializer.initialize(this); // Creamos el objeto MapManager (Codificado en MapManager.java) mMapManager = new MapManager(mPreferencesManager, mMapView); // Asignamos este listener al mapa que estamos esperando. mMapView.getMapAsync(mMapManager); } De este modo, asignamos a la vista un callback contenido en el objeto de la clase MapManager que será ejecutado cuando el mapa esté cargado. De este modo, se ejecutará de manera asíncrona sin ralentizar la interfaz de usuario. Una vez cargado el mapa se ejecutará el método onMapReady que comprobará el estado de la aplicación y, en función del mismo, mostrará una ruta actual o la última ruta realizada. Si cambiara el estado del seguimiento, la actividad principal debe avisarle a esta clase a través del método enableMap puesto que no lo consulta de manera periódica. El resto de métodos de la clase son utilizados para limpiar el contenido del mapa, restaurar los datos de la última ruta y comprobar el estado del mapa. 6.1.4.3 PreferencesManager No hay mucho que comentar acerca de la implementación de esta clase, se trata de un conjunto de métodos 62 Implementación 6.2.2.5.1 Administración Como herramienta de administración, se ha facilitado una sección para añadir usuarios y vehículos (recordemos que se pueden deshabilitar o rehabilitar desde sus respectivas secciones). Esto se realiza mediante un sencillo formulario que controlará que se rellene correctamente mediante JavaScript. Figura 6-13. Sección de administración en la página web 6.2.3 Mapas La implementación de mapas a partir de la API de Google Maps [15] para JavaScript resulta muy interesante a la hora del seguimiento en tiempo real o la consulta de la última posición conocida de algún conductor o vehículo. En primer lugar debemos importar el script JavaScript que queremos utilizar y para ello vamos a incluir la siguiente línea en el elemento <head> del índice de nuestra página. <script src="https://maps.googleapis.com/maps/api/js?key=API_KEY"></script> Debemos sustituir la cadena API_KEY con nuestra clave de la API de Google. A continuación, en la zona del cuerpo HTML en que queramos incluir el mapa, debemos añadir un elemento de bloque que representará el espacio ocupado por el mapa. Gracias a este elemento podremos darle un mejor formato (dimensiones) haciendo uso de CSS. <div id=map-canvas></div> Por tanto, nos referiremos al elemento que contiene al mapa mediante el identificador map-canvas. Cuando queramos cargar el mapa (es decir, en las fichas detalladas), escribiremos la siguiente línea que permite ejecutar un método (initialize) cuando se cargue la ventana. google.maps.event.addDomListener(window, 'load', initialize); En este método lo que haremos será iniciar una serie de variables que representarán tanto el mapa como los elementos que contiene. La inicialización de variables se hace del siguiente modo: var mapOptions = { zoom: 5, center: new google.maps.LatLng(36.1043454, -5.4509973) }; var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions); var coordinates = []; var sessionPath = new google.maps.Polyline({ path: coordinates, geodesic: true, strokeColor: '#FF0000', 63 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos strokeOpacity: 1.0, strokeWeight: 2 }); En el fragmento anterior no aparecen todas las variables que se inicializan pero nos servirá de base para comprender su funcionamiento. Durante el funcionamiento del mapa podremos modificar la posición de los marcadores y la cadena de coordenadas (coordinates) que representa una sesión (sessionPath). En definitiva, estas variables son las que nos permitirán modelar su aspecto inicial y modificar el contenido del mapa en tiempo de ejecución. Además de iniciar variables también podemos declarar funciones, en nuestro caso tendremos las siguientes: function zoomToObject(obj); El principal uso de esta función es encuadrar el dibujo de la ruta dentro del mapa para que aparezca con el zoom adecuado. En resumen, en esta función se crea un objeto google.maps.LatLngBounds incluyendo los puntos que queremos enfocar en el mapa y, a continuación, hacemos uso de la función map.fitBounds(bounds) pasando como parámetro el objeto que hemos construido. function individualRefresh(); Esta función se encarga de leer de la base de datos la ruta que se va a mostrar en el mapa acerca de un elemento. Como JavaScript se ejecuta en el lado del cliente y el acceso a la base de datos debe ser en el servidor, necesitamos hacer uso de la tecnología Ajax que nos permite, de una manera sencilla, hacer peticiones desde JavaScript a PHP. Estas peticiones Ajax se hacen a través de la librería jQuery ya que nos ofrece funciones muy sencillas de comprender. jQuery.ajax({ url: 'ajax.php', type: 'POST', data: {getCoordinates: infoToGet}, success: function(response){ eval("var newcoordinates = [" + response.toString() + "];"); if(newcoordinates.toString() != coordinates.toString()){ sessionPath.setMap(null); coordinates = newcoordinates; sessionPath.setPath(coordinates); sessionPath.setMap(map); marker1['position'] = coordinates[0]; marker2['position'] = coordinates[coordinates.length-1]; marker1.setMap(null); marker1.setMap(map); marker2.setMap(null); marker2.setMap(map); zoomToObject(sessionPath); infowindow1.open(map, marker1); if(section == "rutas"){ 64 Implementación infowindow2.open(map, marker2); } } } }); Como podemos apreciar en el código anterior, especificamos el fichero PHP que va a procesar la petición (ajax.php), el tipo de petición (POST), la función PHP (getCoordinates), el parámetro (infoToGet) y a continuación una función para procesar la respuesta. En esa respuesta, modificamos la cadena de coordenadas y movemos los elementos del mapa en función de los nuevos datos. El parámetro infoToGet es una cadena que se declarará como variable al llamar a la función initialize y tendrá el siguiente formato: sección&elemento. function colectiveRefresh(); Esta función es similar a la anterior aunque orientada a consultar todos los elementos del mismo tipo (todos los conductores o todos los vehículos) y plasmarlos en el mapa mediante marcadores. Estas dos últimas funciones se llamarán periódicamente para actualizar el contenido del mapa a tiempo real15. 6.2.4 Gráficas Las gráficas comparativas se han creado haciendo uso de la librería Highcharts [16] basada en JavaScript. Esta biblioteca es gratuita para uso personal o proyectos sin ánimo de lucro16. A continuación se expone un script similar a los utilizados para lograr los resultados. <script type="text/javascript"> $(function () { $('#container').highcharts({ chart: { type: 'column' }, title: { text: 'Velocidad' }, xAxis: { categories: ['Velocidad (Km/h)'] }, yAxis: { title: { text: 'Km/h' } }, series: [{ name: 'Seleccionado', 15!Ofrece&actualizaciones&cada&5&segundos¶&no&saturar&a&peticiones&al&servidor." 16!Bajo%la%licencia%Creative%Commons%Attribution,NonCommercial*3.0*License." 65 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos data: [velocidad] }, { name: 'Media total', data: [velocidadglobal] }] }); }); </script> Con este código se incluiría una gráfica de barras en el elemento con identificador container que muestre la velocidad de un conductor (por ejemplo) frente a la velocidad media de toda la plantilla de conductores. 6.2.5 Resolución de direcciones Para conseguir la resolución de direcciones a partir de una latitud y longitud se ha creado una función JavaScript que será llamada una vez por cada dirección a resolver. En esta función se hará uso de la API de Google [17]. El método de resolución es el siguiente: var geocoder = new google.maps.Geocoder(); var latlng = new google.maps.LatLng(lat, lon); geocoder.geocode({'latLng': latlng}, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { if (results[0]) { document.getElementById(id.toString()).innerHTML = results[0].formatted_address; } } }); En el código anterior podemos ver que creamos un objeto google.maps.Geocoder que podremos utilizar para conseguir la dirección de un objeto google.maps.LatLng enviado como parámetro. Como respuesta obtendremos una tabla con los resultados ordenado en jerarquía de detalles. El objeto en el índice cero de la tabla tendrá más detalle que cualquier objeto de índice superior. Estos objetos son accesibles y permiten leer la información que buscamos (formatted_address). La información que recibimos viene encapsulada en formato JSON (por defecto, aunque también podemos elegir XML), sin embargo, el uso del objeto google.maps.Geocoder hace el proceso de encapsulado totalmente transparente a nosotros. 6.2.6 Informes En cuanto a la generación de informes en formato CSV, nuestra misión es leer la tabla que el usuario ha decidido proyectar a través de los filtros y volcar esos datos en texto plano siguiendo la estructura de dicho formato. Para ello, basta con leer ordenadamente las celdas de la tabla evitando la última columna (botones de acción). Esto se realiza mediante el siguiente fragmento de código. var table = document.getElementById("table_s"); var numrows = table.rows.length; var numcells = table.rows[1].cells.length; 66 Implementación var csv = "sep=|\n"; for (i=0; i<(numrows); i++){ for(j=0; j<(numcells-1) && (table.rows[i].cells[j] != undefined); j++){ if(table.rows[i].cells[j].innerHTML == "Acción"){ }else{ csv+= table.rows[i].cells[j].innerHTML.replace("<br>", " - ").replace(".", ","); } if (j != (numcells-2)){ csv += "|"; } } csv += "\n"; } A través de este código podemos leer la tabla, almacenar el contenido en una variable (CSV) sustituyendo los elementos que consideremos oportunos (cambiar puntos por comas para evitar confusión) y añadir los separadores en el momento apropiado. El separador de columnas se puede especificar en la primera línea del fichero de salida para que los intérpretes actuales lo interprete correctamente de manera automática. 6.2.7 Acceso a la base de datos La existencia de una base de datos es lo que da sentido a nuestro servidor y, por tanto, sus consultas son el núcleo del mismo. En este apartado vamos a analizar algunas de estas sentencias SQL. • Identificación de un usuario SELECT * FROM USER WHERE user = 'usuario' AND pass = 'contraseña' AND status = true; • Identificación de un vehículo SELECT id_vehicle FROM VEHICLE WHERE mac = 'MACdelVehiculo' AND status = true; • Registrar un usuario INSERT INTO USER (user, pass, status, name, surname) VALUES ( 'usuario' , 'contraseña' , 'estado' , 'nombre' , 'apellidos' ); • Registrar un vehículo INSERT INTO VEHICLE (id_vehicle, mac, model, status, energy) VALUES ( 'matricula' , 'MAC' , 'modelo' , 'estado' , 'consumo' ); • Registrar una sesión INSERT INTO SESSION (id_session, id_vehicle, user) VALUES (NULL, 'matricula' , 'usuario' ); El valor id_session se generará automáticamente y se podrá consultar a continuación mediante la cláusula SELECT LAST_INSERT_ID() FROM SESSION. • Registrar una posición INSERT INTO SESSION_DET (id_session, arrive_order, lat, lon, time_c, date_c, time_s, date_s) VALUES ( sesion , ordendellegada , latitud , longitud , horadelcliente , 67 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos fechadelcliente , horadelservidor , fechadelservidor ) • Habilitar o deshabilitar un usuario UPDATE USER SET status = nuevoestado WHERE user = 'usuario' ; • Habilitar o deshabilitar un vehículo UPDATE VEHICLE SET status = nuevoestado WHERE id_vehicle = 'matricula' ; • Obtener sesiones SELECT id_session FROM SESSION ORDER BY id_session; • Obtener vehículos SELECT id_vehicle, model FROM VEHICLE ORDER BY id_vehicle; • Obtener usuarios SELECT user FROM USER ORDER BY user; • Obtener toda la información de una sesión SELECT * FROM SESSION_DET NATURAL JOIN SESSION NATURAL JOIN USER NATURAL JOIN VEHICLE WHERE id_session = 'sesion' ; Todas estas consultas se realizan mediante PHP del siguiente modo: $servername = "localhost"; $username = "tfg"; $password = "tfgtfg"; $dbname = "TFG"; // Creamos la conexión con la base de datos $conn = new mysqli($servername, $username, $password, $dbname); // Comprobamos la conexión con la base de datos if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = sprintf(" CONSULTA A REALIZAR "); // Ejecutamos la consulta $result = mysqli_query($conn, $sql); // Evaluamos el resultado /* * */ // Cerramos la conexión $conn->close(); 69 7 PRUEBAS n este capítulo se incluyen diferentes pruebas que demuestran el funcionamiento de la herramienta implementada. Para ello, se adjuntan capturas tanto del servidor como de la aplicación cliente en los diferentes estados de funcionamiento. En el lado del cliente, se ha probado la aplicación tanto en un Motorola Moto G con la versión Lollipop de Android como en un Sony Xperia Neo con la versión KitKat. Como dispositivo Bluetooth se ha probado un Parrot CK3100 y ambos teléfonos móviles han detectado correctamente los eventos de conexión y desconexión. En cuanto al servidor, su despliegue e instalación17 se han realizado sobre una Raspberry Pi Model B+ bajo el dominio http://uvejotapi.ddns.net. 7.1 Configuración inicial En primer lugar, una vez instalado todo por primera vez, nuestra base de datos se encontrará vacía y debemos crear las entradas de los usuarios y los vehículos. Para ello, usaremos la pestaña Administrar de la página web. Figura 7-1. Registro de usuarios y vehículos Una vez tenemos creados los conductores y vehículos que nos interesan, ya podemos probar el funcionamiento de la herramienta. Para ello, abrimos la aplicación cliente por primera vez y accederemos a los ajustes de la aplicación para configurar algunos parámetros. 17!El#proceso#de#instalación#tanto#del#servidor#como#del#dominio#se#detalla#en#los#anexos." E 70 Pruebas Figura 7-2. Ajustes en la aplicación móvil Una vez configurados un usuario y contraseña válidos y la dirección URL del servidor con el que queremos comunicarnos, ya tenemos todo listo para empezar. 7.2 Seguimiento de sesiones Si alguno de los conductores asociados comienza un seguimiento, ya sea manual o automático, inmediatamente se añadirá una entrada en la tabla de la sección Rutas de la página web que mostrará información sobre la sesión en curso. Figura 7-3. Entrada en lista de sesiones. No solo ha aparecido una entrada sino que si accedemos al contenido a través de la lupa, veremos que aunque la sesión no está finalizada, podemos ver el recorrido de esta sesión en curso. Esta información se actualiza y permite un seguimiento en tiempo real de manera remota. 71 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos Figura 7-4. Seguimiento en tiempo real en el cliente. Figura 7-5. Seguimiento en tiempo real en el servidor. Una vez la sesión haya finalizado, podemos acceder a la ficha detallada de la misma y comparar la ruta dibujada en el cliente con la almacenada en el servidor para comprobar que el funcionamiento es el esperado. Figura 7-6. Sesión completa en el cliente Figura 7-7. Sesión completa en el servidor 78 Anexos @Override public void onLowMemory() {...} } 9.1.2 Esqueleto de ConnectionService package com.vicente.tfg; import android.app.Service; import android.content.Intent; import android.os.AsyncTask; import android.os.IBinder; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; public class ConnectionService extends Service{ private static ConnectionService mInstance; private HttpClient mHttpClient; private HttpResponse mHttpResponse; private HttpPost mHttpPost; private Integer mSession; private PreferencesManager mPreferencesManager; private String mUrl; private String mUser; private String mPassword; private String mMac; private final static int TIME_BETWEEN_RETRY = 10000; private final static int TIMEOUT_CONNECTION = 5000; private final static int TIMEOUT_SOCKET = 3000; public ConnectionService(){...} @Override public int onStartCommand(Intent intent, int flags, int startId) {...} 79 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos @Override public void onDestroy() {...} private boolean startFlow(){...} public void startTrack(){...} public boolean logIn(){...} public boolean sendMac(){...} public void sendData(final String latitud, final String longitud){...} public void endFlow(){...} public static ConnectionService getHTTPInstance() {...} @Override public IBinder onBind(Intent intent) {...} } 9.1.3 Esqueleto de LocationService package com.vicente.tfg; import android.app.Service; import android.content.Intent; import android.location.Location; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationListener; public class LocationService extends Service implements LocationListener, ConnectionCallbacks, OnConnectionFailedListener { private static LocationService mInstance; private static final String TAG = "LocationService"; public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 8000; public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; private GoogleApiClient mGoogleApiClient; private LocationRequest mLocationRequest; private Location mCurrentLocation; private Boolean mRequestingLocationUpdates; private PreferencesManager preferencesManager; @Override public void onCreate() {...} @Override public int onStartCommand(Intent intent, int flags, int startId) {...} 80 Anexos protected synchronized void buildGoogleApiClient() {...} protected void createLocationRequest() {...} public void startTrack() {...} public void stopTrack() {...} protected void startLocationUpdates() {...} protected void stopLocationUpdates() {...} @Override public void onConnected(Bundle connectionHint) {...} @Override public void onLocationChanged(final Location location) {...} @Override public void onConnectionSuspended(int cause) {...} @Override public void onConnectionFailed(ConnectionResult result) {...} @Override public IBinder onBind(Intent intent) {...} @Override public void onDestroy() {...} public static LocationService getGPSInstance() {...} } 9.1.4 Esqueleto de LocationServiceCompatible package com.vicente.tfg; import android.app.AlertDialog; import android.app.Service; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.provider.Settings; import android.util.Log; public class LocationServiceCompatible extends Service implements LocationListener { private final Context mContext; private static LocationServiceCompatible mInstance; boolean isGPSEnabled = false; boolean isNetworkEnabled = false; boolean canGetLocation = false; public Location mLocation; public Location mCurrentBestLocation; private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 5; private static final long MIN_TIME_BW_UPDATES = 1000 * 7; 81 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos protected LocationManager mLocationManager; private PreferencesManager mPreferencesManager; private static final String TAG = "LocationServiceC"; public LocationServiceCompatible() {...} @Override public int onStartCommand(Intent intent, int flags, int startId) {...} public Location getLocation() {...} public void stopUsingGPS(){...} public void showSettingsAlert(){...} @Override public void onLocationChanged(final Location location) {...} @Override public void onProviderDisabled(String provider) {...} @Override public void onProviderEnabled(String provider) {...} @Override public void onStatusChanged(String provider, int status, Bundle extras) {...} private boolean isBetterLocation(Location location, Location currentBestLocation) {...} private boolean isSameProvider(String provider1, String provider2) {...} public static LocationServiceCompatible getGPSInstance() {...} @Override public void onDestroy() {...} @Override public IBinder onBind(Intent arg0) {...} } 9.1.5 Esqueleto de MapManager package com.vicente.tfg; import android.location.Location; import android.util.Log; import android.view.ViewTreeObserver; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import java.util.ArrayList; public class MapManager implements OnMapReadyCallback { private GoogleMap mGoogleMap; 82 Anexos private PreferencesManager mPreferencesManager; private MapView mMapView; public GoogleMap.OnMyLocationChangeListener mLocationChangeListener; private Boolean mTrigger; private ArrayList<LatLng> mLatLngArrayList; private Polyline mRoute; private int mRouteColor; MapManager(PreferencesManager preferencesManager, MapView mapView){...} @Override public void onMapReady(final GoogleMap googleMap) {...} private void polylineDrawer(LatLng position){...} public void resumeMap(){...} private LatLngBounds showLastRoute(){...} public void clearMap(){...} public void enableMap(Boolean toggle){...} public boolean isMyLocationEnabled(){...} } 9.1.6 Esqueleto de PreferencesManager package com.vicente.tfg; import android.app.ActivityManager; import android.content.Context; import android.content.SharedPreferences; import android.location.Location; public class PreferencesManager { private SharedPreferences mSharedPreferences; public PreferencesListener mPreferencesListener; public Context mContext; private static boolean autoMode = false; public final static String onwait = "Pulse \"Iniciar\" para comenzar una nueva ruta"; public final static String onconnect = "Conectando"; public final static String ontrack = "Rastreando"; public final static String onerror = "Se ha producido un error"; public final static String ondisconnect = "Mostrando ruta realizada"; public final static String onfinish = "Fin del rastreo"; public final static String onretry = "Reintentando"; public final static String onbadlogin = "Error de identificación"; PreferencesManager(Context mContext){} public void setPreferencesListener(Context context){...} public boolean isHttpSession(){...} public boolean isMyServiceRunning(Class<?> serviceClass) {...} public boolean isGpsSession(){...} 83 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos public void clearLastRouteInfo(){...} public void setUrl(String url){...} public String getUrl(){...} public void setUser(String user){...} public String getUser(){...} public void setPass(String pass){...} public String getPass(){...} public void setMac(String mac){...} public String getMac(){...} public static boolean isAutoMode(){...} public static void setAutoMode(boolean auto){...} public void setShowMap(boolean action){...} public void setStatus(String status){...} public boolean isShowMap(){...} public void addToLastRoute(Location location){...} public String getLastRoute(){...} public int getRouteColor(){...} public void setRouteColor(int color){...} } 9.1.7 Esqueleto de PreferencesListener package com.vicente.tfg; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; public class PreferencesListener implements SharedPreferences.OnSharedPreferenceChangeListener { private MainActivity mMainActivity; private final static String TAG = "PreferencesListener"; PreferencesListener(Context context, SharedPreferences sharedPreferences){...} @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {...} } 9.1.8 Esqueleto de SettingsActivity package com.vicente.tfg; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.InputType; import android.view.View; import android.webkit.URLUtil; 84 Anexos import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; public class SettingsActivity extends Activity { private TextView mEditUser; private TextView mEditPass; private TextView mEditUrl; private Switch mMapToggle; PreferencesManager mPreferencesManager; @Override protected void onCreate(Bundle savedInstanceState) {...} private void setAll(){...} public void onClickCredentialsButton(final View view){...} public void onClickServerButton(View view){...} public void onClickMapToggle(View view){...} } 9.1.9 Esqueleto de BluetoothManager package com.vicente.tfg; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class BluetoothManager extends BroadcastReceiver { private final static String TAG = "BluetoothManager"; public Context mContext; public BluetoothManager() {...} @Override public void onReceive(Context context, Intent intent) {...} } 9.1.10 Diagrama de clases completo 85 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos 86 Anexos Figura 9-1. Diagrama de clases del cliente 9.2 Servidor 9.2.1 Despliegue del servidor Para poder llevar a cabo las pruebas realizadas, se ha utilizado una Raspberry Pi bajo una distribución Linux basada en Debian Wheezy. Aunque se trata de un dispositivo de baja potencia, ha sido más que suficiente para realizar las comprobaciones de funcionamiento correcto. El servidor HTTP que se ha desplegado sobre el equipo es Nginx, una tecnología gratuita, de código libre y alto rendimiento. Aunque la instalación de este software puede variar ligeramente de un dispositivo a otro, a continuación adjunto algunos pasos a seguir si queremos instalarlo en un dispositivo bajo la distribución Debian. En primer lugar, haciendo uso de la herramienta apt-get (necesitamos los repositorios al día), hacemos lo siguiente. root@equipo:~# apt-get install nginx php5-fpm php-apc De este modo hemos instalado nginx y lo necesario para interpretar PHP. A continuación, debemos instalar la base de datos. root@equipo:~# apt-get install mysql-client mysql-server php5-mysql Y ya tendremos los paquetes necesarios instalados. En cuanto a la configuración de los mismos, dependerá de las preferencias de cada usuario aunque en todo caso debemos modificar el fichero de configuración de nuestra web alojado en /etc/nginx/sites-available/midominio.conf, en nuestro caso y como sólo se trata de un prototipo, hemos utilizado el fichero default que viene por defecto aunque es recomendable crear uno aparte. A continuación se adjunta el fichero de configuración que se ha utilizado para las pruebas. server { listen 80; ## listen for ipv4; this line is default and implied #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 root /usr/share/nginx/www; index index.php index.html index.htm; # Make site accessible from http://localhost/ server_name localhost; location / { } location /doc/ { alias /usr/share/doc/; autoindex on; allow 127.0.0.1; allow ::1; deny all; } 87 Implementación de una herramienta distribuida para el seguimiento de una flota de vehículos location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; # With php5-fpm: fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # location ~ /\.ht { deny all; } } 9.2.2 Despliegue de la base de datos Una vez desplegado el servidor y con PHP funcionando, debemos crear nuestra base de datos. Para ello, accedemos a MySQL con la contraseña de superusuario que nos pidió crear al instalar el paquete. mysql -u root -p Una vez aceptada, entraremos en la consola MySQL y en primer lugar deberíamos crear un usuario y contraseña para leer la base de datos que vamos a crear a continuación. Para ello, podemos crear un fichero SQL con el siguiente contenido e interpretarlo mediante MySQL. CREATE DATABASE IF NOT EXISTS TFG; USE TFG; CREATE TABLE USER ( user VARCHAR(30) PRIMARY KEY, pass VARCHAR(40) NOT NULL, status BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, surname VARCHAR(255) NOT NULL ); CREATE TABLE VEHICLE ( id_vehicle VARCHAR(40) PRIMARY KEY, mac VARCHAR(40) NOT NULL, model VARCHAR(40) NOT NULL, status BOOLEAN NOT NULL, energy INT(10) ); CREATE TABLE SESSION ( id_session INT(10) AUTO_INCREMENT PRIMARY KEY, id_vehicle VARCHAR(40),