{"id":15648778,"url":"https://github.com/mdelapenya/uned-sensors","last_synced_at":"2026-04-29T18:33:19.208Z","repository":{"id":141610097,"uuid":"78963453","full_name":"mdelapenya/uned-sensors","owner":"mdelapenya","description":null,"archived":false,"fork":false,"pushed_at":"2017-04-06T12:01:20.000Z","size":1055,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T00:11:08.705Z","etag":null,"topics":["android","android-application","iot","uned"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mdelapenya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-14T19:59:57.000Z","updated_at":"2024-07-02T19:44:50.000Z","dependencies_parsed_at":"2023-06-25T22:51:01.878Z","dependency_job_id":null,"html_url":"https://github.com/mdelapenya/uned-sensors","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mdelapenya/uned-sensors","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdelapenya%2Funed-sensors","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdelapenya%2Funed-sensors/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdelapenya%2Funed-sensors/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdelapenya%2Funed-sensors/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mdelapenya","download_url":"https://codeload.github.com/mdelapenya/uned-sensors/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdelapenya%2Funed-sensors/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32439179,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T18:12:22.909Z","status":"ssl_error","status_checked_at":"2026-04-29T18:11:33.322Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-application","iot","uned"],"created_at":"2024-10-03T12:26:22.414Z","updated_at":"2026-04-29T18:33:19.190Z","avatar_url":"https://github.com/mdelapenya.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Práctica Sensores\n\n    Alumno: Manuel de la Peña\n    D.N.I.: 53.430.012-T\n    Asignatura: Computación Ubícua\n    Máster de Investigación en Ingeniería del Software y Sistemas Informáticos\n    Curso: 2016-2017\n    Universidad Nacional de Educación a Distancia (U.N.E.D.)\n\nPara acceder en modo online a este documento, por favor visite [el siguiente enlace](https://github.com/mdelapenya/uned-sensors/blob/master/README.md)\n\n## Enunciado de la práctica\n\nDiseñar e implementar un nuevo sensor (denominado MIVELOCIDAD) “virtual” que devolverá un valor\n(enumerado) según la velocidad a la que se desplace el dispositivo que lo utilice.\n\n## Descripción de la práctica\n\nLa práctica consiste en el diseño de un nuevo sensor MIVELOCIDAD que devolverá el estado de velocidad\nal que se encuentra el dispositivo en el que se encuentra instalado.\n\nEl nuevo sensor MIVELOCIDAD define los estados de velocidad de acuerdo a los siguientes criterios:\n\n* Velocidad Mínima 0km/h – Velocidad Máxima 1Km/h: ESTADO PARADO.\n* Velocidad Mínima 1km/h – Velocidad Máxima 4Km/h: ESTADO CAMINANDO\n* Velocidad Mínima 4km/h – Velocidad Máxima 6Km/h: ESTADO MARCHANDO\n* Velocidad Mínima 6km/h – Velocidad Máxima 12Km/h: ESTADO CORRIENDO\n* Velocidad Mínima 12km/h – Velocidad Máxima 25Km/h: ESTADO SPRINT\n* Velocidad Mínima 25km/h – Velocidad Máxima 170Km/h: ESTADO VEH. MOTOR TERRESTRE\n* Velocidades Mayores 170Km/h: ESTADO VEH. MOTOR AÉREO\n\nEl sensor dispondrá de una configuración para delimitar tanto el estado inicial (fijando un determinado\nintervalo de tiempo para establecer el valor inicial) como las bandas muertas existentes en los límites\nentre los cambios de estado (tanto por arriba como por abajo) ya sea por recogida de valores o por\ntiempos. Por ejemplo, para cambiar de estado de CORRIENDO a SPRINT puede configurarse un tipo de banda\nmuerta de 1500 msg. de valores en el estado de CORRIENDO para cambiar a SPRINT y de 500 msg en estado\nde SPRINT para cambiar a CORRIENDO.\n\n## Arquitectura de la solución\n\nExisten numerosos enfoques para abordar el desarrollo de una aplicación en un dispositivo móvil. Para\ncomenzar, es necesario elegir la plataforma de desarrollo. Actualmente existen iOS, Android y Windows\nPhone como principales plataformas de desarrollo, aunque existen alguna más con una horquilla del\nmercado extremadamente reducida en comparación con las tres mencionadas, como podría ser BlackBerry.\n\nDebido a la facilidad de acceso a un dispositivo *Android*, así como el aprovechamiento del conocimiento\ndel lenguaje Java por parte del desarrollador, se ha optado por realizar el desarrollo de la práctica\nbajo la tecnología **Android**, de modo que para poder probar la aplicación será necesario disponer\nde un terminal con este sistema operativo móvil.\n\nLa aplicación ha sido desarrollada según los patrones de desarrollo de *Android*, por el cual las vistas\nse encapsulan en clases de tipo **Activity**. Estas *activities* serán las responsables de disparar\nla lógica de negocio de la aplicación, así como de responder ante los eventos disponibles en el\nterminal, como pueden ser los cambios de orientación, cambios de posición, etc.\n\nPara el caso que nos ocupa, la actividad principal responderá ante los cambios de posición, y en cada\nuno de estos cambios, leerá del sensor hardware del dispositivo el valor de la velocidad actual.\n\nAdemás, la aplicación permitirá definir unos rangos de velocidades, de modo que se pueda identificar\nel rango de velocidad en el que se encuentra el sensor del dispositivo, comparando la velocidad actual\ncon los valores límite establecidos para cada uno de los rangos, determinando de este modo el rango\nde velocidad en el que se encuentra el dispositivo.\n\nEstos rangos tendrán unos valores límite, valores mínimo y máximo, que determinen el rango de velocidad,\nasí como un nombre que lo identifique.\n\n## Diseño del sensor\n\nPara el diseño del sensor se han valorado las siguientes aproximaciones, todas relacionadas con el tipo\nde sensor a utilizar para obtener los datos.\n\nLa primera es utilizar los valores del sensor que mide los cambios en el acelerómetro. Este sensor,\nque en los dispositivos Android se identifica por *Sensor.TYPE_ACCELEROMETER*, obtiene los cambios\nproducidos sobre el sensor Acelerómetro.\n\nLa segunda es utilizar los valores del sensor que mide los cambios en la velocidad linear. Este sensor,\nque en los dispositivos Android se identifica por *Sensor.TYPE_LINEAR_ACCELERATION*, obtiene los cambios\nproducidos sobre el sensor Acelerómetro eliminado la componente de la gravedad.\n\nPor último, se podrían utilizar los valores obtenidos del GPS del dispositivo, facilitados por las\nlibrerías de *Google Play Services*. En estas librerías se tiene acceso a los datos de localización\nobtenidos del chip GPS del dispositivo, y en función a la posición actual y anterior, calcular la\nvelocidad instantánea del mismo, que es un valor leído directamente del GPS del dispositivo.\n\nSi optásemos por la lectura desde los sensores del acelerómetro o de la aceleración lineal, nos obligaría\na constantemente leer del hardware para actualizar los datos de representación en la pantalla\ndel terminal, lo cual consumiría muchos recursos, sobre todo de batería. En cambio, si utilizásemos\nlos valores obtenidos desde el GPS, a través de los servicios de *Google Play Services*, reduciríamos\nla frecuencia de actualización de esas lecturas, ocurriendo ésto en el evento de cambio de ubicación\ndel GPS. Es importante destacar que este evento de cambio de la posición ocurre con con mucha menor\nfrecuencia que los eventos asociados a los sensores físicos comentados con anterioridad.\n\nPor esta razón, la aplicación desarrollada utiliza los servicios de *Google Play Services* para obtener\nlos datos de posición del dispotivo, obteniendo la velocidad instantánea directamente desde este API.\n\n### Limitaciones del sensor\n\nEs importante conocer que el uso de estos servicios de localización tienen la misma limitación que un\nGPS convencional, esto es, el **mal funcionamiento en interiores**, de modo que para disfrutar de la\nmejor experiencia de uso de la aplicación es conveniente utilizarla en exteriores, con cobertura GPS.\n\n## Desarrollo\n\n### Lenguaje de programación\n\nEl proceso de desarrollo viene definido por el desarrollo de aplicaciones Android, por lo que es\nnecesario la instalación de un SDK de Android, así como tener la versión adecuada de Java respecto al\nSDK anterior.\n\nDe este modo, el SDK de Android utilizado es la versión 23.0.3, utilizando Java 8 en su versión 1.8.0_45.\n\n### Sistema de Build\n\nPara la construcción del proyecto, tal y como es habitual en los proyectos *Android*, se ha utilizado\n**Gradle**, en su versión 3.3.\n\n#### Alternativas en el sistema de build para aplicaciones de la JVM\n\nEl ecosistema JVM se encuentra dominado por tres herramientas de build:\n\n* Apache Ant con Ivy como gestor de dependencias\n* Maven\n* Gradle\n\n##### Apache Ant + Ivy\n\n`Ant` fue la primera herramienta moderna de build. En muchos aspectos es similar a `Make`. Fue lanzada\nen el año 2000, y en un periodo corto de tiempo consiguió ser la herramienta de build más popular para\nproyectos Java. Tiene una curva de aprendizaje pequeña, lo que permite a cualquiera comenzar a utilizarla\nsin ninguna preparación especial, Está basado en la idea de la programación procedural.\n\nTras su lanzamiento inicial, fue mejorada con el soporte para añadir plugins.\n\nPor otro lado, su mayor incoveniente siempre ha sido el uso de XML como formato para la escritura de\nlos scripts de construcción. XML, debido a su naturaleza jerárquica, no es un buen candidato para el\nenfoque procedural de programación que `Ant` utiliza. Otro problema importante con `Ant` es que el\nXML que utiliza tiende a convertirse en inmanejablemente grande, tanto en proyectos grandes como\npequeños.\n\n##### Maven\n\n`Maven` fue lanzado en 2004. Su objetivo era el mejorar los problemas que los desarrolladores tenían\nal usar `Ant`. Continúa utilizando XML como el formato de escritura de los scripts de construcción,\nsin embargo es diametralmente diferente a `Ant` en su estructura: mientras `Ant` obliga a los\ndesarrolladores a escribir todos los comandos que llevan a la ejecución satisfactoria de algunas tareas,\n`Maven` se apoya en convenciones y proporciona unos `target` (goals, u objetivos) que pueden ser\ninvocados. Otras mejoras, y problamente las más importantes, son que `Maven` introdujo la habilidad\nde descargar dependencias de la red, que luego `Ant` adoptó mediante el proyecto `Apache Ivy`. Este\nnuevo enfoque de gestión de dependencias revolucionó la manera de entregar software.\n\nSin embargo, `Maven` tiene sus propios problemas. La gestión de dependencias que hace no maneja de\nmanera correcta los conflictos entre diferentes versiones de la misma librería, algo en lo que `Ivy`\nes mucho mejor. Además, XML como formato de configuración es muy estricto en su estructura, así como\nmuy estandarizado. La personalización de los `targets` es compleja, por ello, al estar `Maven` más\nenfocado en la gestión de las dependencias, es más dificil escribir complejos scripts de construcción\npersonalizados en `Maven` que en `Ant`.\n\nEl principal beneficio de utilizar `Maven` es su ciclo de vida. Mientras un proyecto se adhiera a unos\nestándares, con `Maven` se puede adaptar el ciclo de vida con cierta facilidad. Como contrapartida,\nesta adaptación disminuye la flexibilidad.\n\n##### Gradle\n\nHoy día existe cierto interés creciente en los DSLs (Domain Specific Languages), en los que la idea\nes disponer de lenguajes diseñados específicamente para solucionar problemas de cierto dominio. Aplicado\nal mundo de los sistema de build, uno de los resultados de aplicar DSL es `Gradle`.\n\n`Gradle` combina las buenas partes de las dos herramientas anteriores y construye sobre ellas utilizando\nun DSL, entre otras mejoras. Dispone de la potencia y la flexibilidad de `Ant`, así como la facilidad\na la hora de definir un ciclo de vida de `Maven`. El resultado final es una herramienta que fue\nlanzada en 2012, y obtuve mucha atención en muy poco tiempo. Por ejemplo, Google adoptó `Gradle` como\nherramienta de build por defecto para el sistema operativo Android.\n\n`Gradle` no utiliza XML. En su lugar, tiene su propio DSL basado en `Groovy`, un lenguaje de la JVM.\nComo resultado, los scripts de construcción de `Gradle` tienden a ser mucho más pequeños y limpios\nque aquéllos escritos con `Ant` o `Maven`. La cantidad de código repetitivo es mucho menor, puesto\nque su DSL está diseñado para resolver un problema en concreto: mover el software a través de su ciclo\nde vida, desde la compilación, pasando por el análisis estático de código y el testing, hasta el\nempaquetado y el despliegue. En sus inicios, `Gradle` utilizaba `Apache Ivy` para la gestión de\ndependencias, pero más adelante pasó a utilizar un motor de resolución propio.\n\nLos esfuerzos de `Gradle` se podrían resumir en la frase “la convención es buena, así como la flexibilidad”.\n\nGradle proporciona:\n\n* una herramienta de construcción de propóstio general y muy flexible, parecido a Apache Ant.\n* proporciona frameworks intercambiables, basados en construcción-por-convención, al estilo de Maven.\n* soporte de construcción de proyectos multi-proyecto muy potente.\n* gestión de dependencias muy potente, basado en Apache Ivy.\n* soporte completo para infraestructuras de repositorios Maven o Ivy existentes.\n* soporte para gestión de dependencias transitivas, sin la necesidad de repositorios remotos, o ficheros\n`pom.xml` o `ivy.xml`.\n* tareas Ant y construcciones tratadas como ciudadanos de primera clase.\n* scripts de construcción de `Groovy`.\n* un modelo de dominio muy rico para describir el sistema de construcción de cada proyecto.\n\nEn el caso concreto de Android, para el proyecto no se utiliza nada fuera del estándar, basando el\nsistema de construcción en el *default* que ofrece el plugin de Android. Por ello, las tareas de\nconstrucción utilizadas son: `clean`, `assemble`, `test`.\n\nEl diagrama de interacción entre las diferentes tareas de Gradle es el siguiente:\n\n![Diagrama de tareas de Gradle](./static/gradle_diagram.png)\n\nEn cuanto a la gestión de dependencias, se utilizan tanto los repositorios de `jcenter` como la\ninstalación local de Maven, ubicada en `$USER_HOME/.m2`.\n\n### Organización del código\n\nAl utilizar Gradle como sistema de build, el proyecto sigue un `layout` específico determinado por la\nconvención de nombres y directorios propia de Gradle.\n\nSegún esta convención, la aplicación está dentro de un directorio `app`, y dentro de este directorio\nexistirá un `src`, así como algunos ficheros descriptores, como por ejemplo el `build.gradle`. Dentro\nde `src` se sigue una estructura igual a la definida por `Maven`:\n\n* `src/main` para el descriptor principal de la aplicación Android, `AndroidManifest.xml`\n* `src/main/java` para el código de la aplicación\n* `src/main/res` para los recursos estáticos de la aplicación: layouts de Android, Strings, etc.\n* `src/main/test` para los tests unitarios\n* `src/main/testIntegration` para los tests de integración\n* `src/main/androidTest` para los tests de interfaz de usuario de Android\n\nEn la siguiente imagen aparecen los elementos antes mencionados:\n\n![Estructura de proyecto en Gradle](./static/gradle_project_layout.png)\n\nPor otro lado, en el desarrollo de la aplicación se ha utilizado una estructura de paquetes adecuada\npara realizar la separación lógica entre los diferentes componentes de la misma.\n\nA continuación se enumeran los paquetes de la aplicación, que como hemos mencionado antes, se ubican\nbajo el directorio `app/src/main/java` del proyecto.\n\n#### Actividades\n\nLas clases que aquí se encuentran representan la vista de las aplicaciones Android. Se encuentran bajo\nel paquete `es.mdelapenya.uned.master.is.ubicomp.sensors.activities`.\n\nUna actividad es una única cosa que un usuario puede hacer. Casi todas las actividades interaccionan\ncon el usuario, por tanto la clase Activity de Android se responsabiliza de crear una ventan en la\nque ubicar la interfaz de usuario de las aplicaciones Android. Para hacer ésto, se dispone del método\n`setContentView(View)`. Del mismo modo que a menudo las actividades son presentadas al usuario como\nventanas a tamaño completo, también pueden ser utilizadas de otras maneras: como ventanas flotantes\n(a través de un tema de apariencia), o empotradas dentro de otra actividad, utilizando *ActivityGroup*.\nExisten dos métodos que casi todas las actividades implementarán:\n\n* **onCreate(Bundle)**: inicializa la actividad, y lo que es más importante, normalmente invocará el método\n`setContentView(int)` con un recurso de tipo *layout* definiendo la interfaz de usuario, y utilizando\nel método `findViewById(int)` encontrará los widgets de la UI que se necesitarán a la hora de interactuar\ncon ellos de manera programática.\n* **onPause()**: es donde se gestiona el momento en el que el usuario deja de utilizar la actividad, y lo\nque es lo más importante, cualquier cambio realizado por el usuario debería ser comiteado en este\nmomento, normalmente mediante un `ContentProvider` que mantenga el acceso a los datos.\n\nEn el caso concreto de la aplicación, existen 4 actividades: BaseGeoLocatedActivity, MainActivity,\nRangeListActivity, RangeDetailActivity, que detallaremos a continuación.\n\n##### BaseGeoLocatedActivity\n\nEsta actividad contiene el código responsable de responder a los eventos de localización, manteniendo\nen su estado interno la última localización conocida del dispositivo, y que comparará con la actual\npara obtener la velocidad instantánea.\n\nAdemás, inicializa los servicios de *Google Play Services*, así como gestiona de una manera *lazy*\nlos permisos que la aplicación necesita parara funcionar, como es el acceso a la localización. Por\n*lazy* se entiende que los permisos serán solicitados al usuario no en el momento de la instalación\nde la aplicación, donde el usuario posiblemente no tenga el conocimiento suficiente para tomar una\ndecisión acertada sobre la instalación de la misma, sino en el momento del primer uso del permiso.\n\n##### MainActivity\n\nEsta actividad es la actividad principal de la aplicación. Extenderá la actividad anterior para acceder\na las capacidades de localización, y las extenderá para actualizar la propia UI.\n\nActualizará la interfaz de usuario en cuanto se haya producido un cambio en la localización y la\nvelocidad haya cambiado. Para ello, la actividad dispone del siguiente método, que determina si tiene\nque actualizar la interfaz en los casos en que el estado anterior sea diferente del estado actual:\n\n```java\n    private TextView currentSpeed;\n    private TextView currentSpeedText;\n    private String oldSpeed = \"0.00\";\n    private String oldSpeedText = \"\";\n    ...\n    ...\n    private void updateUI() {\n        float speedKm = SpeedConverter.convertToKmsh(getSpeed());\n\n        String rangeName = getRangeName(speedKm);\n        String speedValue = roundSpeed(speedKm);\n\n        if (!UIManager.syncUIRequired(speedValue, oldSpeed, rangeName, oldSpeedText)) {\n            return;\n        }\n\n        currentSpeed.setText(speedValue);\n        currentSpeedText.setText(rangeName);\n\n        oldSpeed = speedValue;\n        oldSpeedText = rangeName;\n    }\n```\n\nSiendo `currentSpeed` y `currentSpeedText` dos componentes gráficos de Android representando etiquetas\nde presentación de datos, y `oldSpeed` y `oldSpeedText` el estado interno de la actividad, en el que\ncada vez que se detecte un cambio de posición se almacenerán los valores de la velocidad y el nombre\ndel rango en el que se encuentra.\n\nEl método `getRangeName(float)` determina si la velocidad actual se encuentra en alguno de los rangos\nexistentes en la aplicación. Para ello, se iterará por los rangos, y se comprobará que la velocidad\nse encuentra entre los valores mínimo y máximo del rango. Al existir un único rango para cada valor\nmínimo o máximo, una velocidad dada sólo podrá estar dentro de un único rango. El método devolverá\nel nombre del rango, primero buscando entre los recursos de tipo String de la aplicación, y si no\nexistiese, directamente el nombre del rango. Si, por otro lado, la velocidad no se encuentra dentro\nde ningún rango, devolverá como valor por defecto el valor de la clave *\"PARADO\"*.\n\n```java\n    private String getRangeName(float currentSpeed) {\n        for (Range range : ranges) {\n            if (range.isInRange(currentSpeed)) {\n                int resourceByName = ResourceLocator.getStringResourceByName(this, range.getName());\n\n                if (resourceByName \u003e 0) {\n                    return this.getString(resourceByName);\n                }\n\n                return range.getName();\n            }\n        }\n\n        return this.getString(R.string.status_stopped);\n    }\n```\n\n##### RangeListActivity\n\nEsta actividad, invocada desde la actividad principal desde la barra de navegación, administrará las\noperaciones de creación, modificación y borrado de los rangos disponibles en la aplicación.\n\nPresentará una lista con los rangos en la base de datos, donde se presentará un botón de *Nuevo Rango*,\nse podrá editar un rango existente simplement pulsando en la fila del rango, o se podrá borrar un\nrango, mediante la acción de `swipe`, tanto a izquierdas como a derechas, sobre la fila que representa\na un rango.\n\n##### RangeDetailActivity\n\nEsta actividad, invocada desde la pulsación de un elemento en la fila de rangos, mostrará un formulario\ncon todos los datos del rango seleccionado: nombre, valor mínimo y valor máximo del rango. Desde esta\nactividad se podrán guardar los datos asociados al rango, tanto para un rango nuevo (creación) como\npara uno existente (actualización).\n\nEn ambos casos existirá un proceso de validación, que comprobará que no existan rangos con valores\nmínimos o máximos duplicados, de modo que sólo pueda existir un rango con un valor mínimo o máximo\ndado.\n\n```java\n    public boolean isValidationSuccess() {\n        if (\"\".equals(range.getName())) {\n            return false;\n        }\n\n        RangeService rangeService = new RangeService(getContext());\n\n        List\u003cRange\u003e minRanges = rangeService.findBy(\n            new CriterionImpl(RangeDBHelper.Range.COLUMN_NAME_MIN, range.getMin()));\n\n        if (checkDuplicates(minRanges)) {\n            txtMin.requestFocus();\n\n            return false;\n        }\n\n        List\u003cRange\u003e maxRanges = rangeService.findBy(\n            new CriterionImpl(RangeDBHelper.Range.COLUMN_NAME_MAX, range.getMax()));\n\n        if (checkDuplicates(maxRanges)) {\n            txtMax.requestFocus();\n\n            return false;\n        }\n\n        return true;\n    }\n\n    private boolean checkDuplicates(List\u003cRange\u003e ranges) {\n        if (ranges.size() == 0) {\n            return false;\n        }\n\n        if (ranges.get(0).getId() == range.getId()) {\n            return false;\n        }\n\n        return true;\n    }\n```\n\n#### Persistencia\n\nLas clases que aquí se encuentran representan la capa de persistencia de la aplicación. Se encuentran\nbajo el paquete `es.mdelapenya.uned.master.is.ubicomp.sensors.internal.db`.\n\nLas clases que extiendan a la clase `SQLiteOpenHelper` se corresponden con clases de utilidad para\nmanejar tanto la creación de una base de datos como el versionado de la misma.\n\nEs posible que estas subclases implementen los métodos `onCreate(SQLiteDatabase)` y\n`onUpgrade(SQLiteDatabase, int, int)`, y de manera opcional `onOpen(SQLiteDatabase)`. Estas clases se\nresponsabilizarán de abrir la base de datos en caso de que exista, de crearla si no existe, y de\nactualizarla si fuese necesario. Las transacciones están soportadas.\n\nEn la aplicación, dispondremos de la clase `RangeDBHelper`, que es una clase con dos responsabilidades\nclaramente definidas:\n\n* la creación física de la base de datos, utilizando como sistema gestor de bases de datos **SQLite**,\npequeña base de datos en memoria muy utilizada en desarrollos Android.\n* la definición de la estructura de las entidades a almacenar en la base de datos, en este caso el\nmodelo de Rangos.\n\n##### Operaciones CRUD\n\nPor otro lado, la clase `RangeDAO`, que sigue el patrón DAO (*Data Access Object*), y representa las\noperaciones de manipulación de datos al nivel de la base de datos. Estas operaciones serán, además de\nlas básicas de CRUD (alta, baja, y modificación), la de búsqueda. Esta clase implementa la interfaz\n`Closeable`, de modo que existirá un método `close()` que se invocará de manera automática en los\nbloques *finally*, de modo que se permitan cerrar los recursos sin tener que repetir el código de\ncerrado cada vez que se utilice. En este caso de clase de acceso a datos, se cerrarán las conexiones\nrealizadas a la base de datos.\n\n```java\n    @Override\n    public void close() {\n        dbHelper.close();\n    }\n```\n\n##### Búsquedas\n\nPara implementar las búsquedas, se ha definido una interfaz `Criterion`, que siguiendo el patrón de\ndesarrollo `Strategy`, permite al DAO definir una búsqueda u otra en función del criterio de utilizado.\nEste criterio definirá como estado interno un campo de búsqueda y el valor asociado a ese campo. Se\nofrece una implementación por defecto de la interfaz, de modo que se puedan crear criterios y buscar\npor ellos.\n\nEsta capa de persistencia nunca deberá ser invocada directamente, puesto que será preferible abstraerla\nmediante la capa de servicios, descrita a continuación. De esta manera los consumidores de los datos\nno necesitan conocer la implementación de la base de datos, sino que simplemente llaman a un servicio\npara obtenerlos.\n\n#### Servicios\n\nLas clases que aquí se encuentran representan la capa de servicios de la aplicación. La interfaz del\nservicio se encuentra bajo el paquete `es.mdelapenya.uned.master.is.ubicomp.sensors.services`, y la\nimplementación por defecto se encuentra en el paquete\n`es.mdelapenya.uned.master.is.ubicomp.sensors.internal.services`.\n\nEl único servicio que ofrece la aplicación, `CRUDService` es un servicio para encapsular las operaciones\nde acceso a datos, y en su interfaz define dichas operaciones.\n\n```java\n    public interface CRUDService\u003cT\u003e {\n\n        T add(T t);\n\n        void delete(T t);\n\n        List\u003cT\u003e findBy(Criterion criterion);\n\n        T get(long id);\n\n        List\u003cT\u003e list();\n\n        T update(T t);\n\n    }\n```\n\nSe ha desarrollado con *Generics* de Java, lo cual permite reutilizar la interfaz con cualquier tipo\nde modelo.\n\nLa implementación, `RangeService`, invoca a la capa de persistencia en cada uno de sus métodos, de\nmodo que abstraiga la implementación de la base de datos, invocando siempre al servicio en su lugar.\n\n#### Modelos\n\nLas clases que aquí se encuentran representan el modelo de la aplicación. Se encuentran bajo el paquete\n`es.mdelapenya.uned.master.is.ubicomp.sensors.model`.\n\nEn este caso, la aplicación contiene únicamente un solo modelo, definido en la clase `Range`, que no\nes más que un POJO (*Plain Old Java Object*) con los campos necesarios para formar un Rango. Estos\ncampos son:\n\n* rangeId: identificador único del rango, obtenido de persistir el modelo en la base de datos.\n* name: identifica en lenguaje natural el rango, pudiendo permitir duplicados.\n* min: valor mínimo del rango. Debe ser **único** entre todos los rangos.\n* max: valor máximo del rango. Debe ser **único** entre todos los rangos.\n\n#### Comunicación con la plataforma IoT\n\nPara la implementación de la comunicación con la plataforma IoT, se ha utilizado un modelo de programación\nasíncrona, de modo que no se bloquea el hilo principal de ejecución de Android. Para ello, el código\nse apoya en dos librerías de utilidad.\n\nPor un lado se utiliza [Retrofit](http://square.github.io/retrofit/) se utiliza para el envío de métricas\na la plataforma IoT. Retrofit permite mapear un API HTTP a interfaces Java. Y por otro, para el paso\nde mensajes de manera asíncrona, se utiliza [Otto](http://square.github.io/otto/), que es un bus de\neventos para desacoplar diferentes partes de la aplicación permitiendo al mismo tiempo que se comuniquen\nentre ellos de manera eficiente.\n\nEn cuanto al código Java de implementación, se ha seguido un patrón `Interactor`, que es el responsable\nde implementar el patrón MVC (*Model-View-Controller*) en Android. El `Interactor` enviará datos desde\nla aplicación al servicio remoto.\n\nEl interactor, que en su estado interno dispone de una métrica, utilizará `Retrofit` para invocar el\nservicio y realizar una petición `POST`, con los datos de dicha métrica.\n\n```java\n    private static final String BACKEND_ENDPOINT = \"http://api.mdelapenya-sensors.wedeploy.io\";\n\n    @Override\n    public void run() {\n        try {\n            Retrofit retrofit = new Retrofit.Builder()\n                .baseUrl(BACKEND_ENDPOINT)\n                .addConverterFactory(JacksonConverterFactory.create())\n                .build();\n\n            SensorsService sensorsService = retrofit.create(SensorsService.class);\n\n            Call\u003cString\u003e stringCall = getResponse(sensorsService);\n\n            Response\u003cString\u003e response = stringCall.execute();\n\n            Object event = new Error(response.message());\n\n            if (response.isSuccessful()) {\n                event = response.body();\n            }\n\n            AndroidBus.getInstance().post(event);\n        }\n        catch (IOException e) {\n            AndroidBus.getInstance().post(e);\n        }\n    }\n```\n\nPara la comunicación en segundo plano, se ha implementado un Bus de mensajes en la clase `AndroidBus`,\nbasada en la librería `Otto`.\n\n#### Clases de Utilidad\n\nCon el mero fin de encapsular el código, bajo el paquete `es.mdelapenya.uned.master.is.ubicomp.sensors.util`\nse han creado varias clases de utilidad, que definen ciertas operacion de manera aislada.\n\nEstas clases son:\n\n* ResourceLocator, que permite la obtención de recursos de Android, ya sean de tipo String, o imágenes.\n* SpeedConverter, que convierte una velocidad en metros por segundo a kilómetros por hora.\n* UIManager, que determina si la interfaz de usuario (UI) debe ser actualizada en función de los\nparámetros de entrada. Estos parámetros se corresponden con los valores anteriores y actualies de\nvelocidad.\n\nEl motivo principal de esta encapsulación es el de poder probar la funcionalidad definida en estas\nclases, de modo que se puedan escribir *tests unitarios* de las mismas.\n\n#### Eventos\n\nPara obtener la velocidad desde el GPS del dispositivo tendremos que suscribirnos a determinados\neventos, de modo que cuando sean disparados, podemos realizar alguna acción.\n\nPara el caso que nos ocupa, la actividad **MainActivity** deberá suscribirse al evento de cambio de\nubicación del GPS, tal y como se observa en el siguiente bloque de código:\n\n```java\n    @Override\n    public void onLocationChanged(Location location) {\n        super.onLocationChanged(location);\n\n        updateUI();\n    }\n```\n\nTal y como comentamos al detallar las actividades de la aplicación, la aplicación dispone de una clase\nbase **BaseGeoLocatedActivity**. Esta clase es la responsable de encapsular el código necesario para\nacceder a los servicios de *Google Play Services*, y con ellos acceder a los servicios de localización.\nCada vez que cambie la localización, se ejecutará su método `onLocationChanged(Location)`, que calculará\nla velocidad instantánea a partir de un cálculo con las coordenadas de la localización actual y la\nanterior conocida, y el tiempo transcurrido entre ambas mediciones.\n\nUna vez se haya actualizado la localización actual, se enviará una métrica a la plataforma IoT.\n\n```java\n    @Override\n    public void onLocationChanged(Location location) {\n        Location currentLocation = LocationServices.FusedLocationApi.getLastLocation(\n            googleApiClient);\n\n        double speed = 0;\n\n        if (lastLocation != null) {\n            speed = Math.sqrt(\n                Math.pow(\n                    currentLocation.getLongitude() - lastLocation.getLongitude(), 2) +\n                    Math.pow(currentLocation.getLatitude() - lastLocation.getLatitude(), 2)\n                ) / (currentLocation.getTime() - lastLocation.getTime());\n\n            if (currentLocation.hasSpeed()) {\n                speed = currentLocation.getSpeed();\n            }\n\n            this.speed = new Float(speed);\n\n            lastLocation = currentLocation;\n\n            Metric metric = new SensorMetric(\n                uniqueDeviceId, \"sensors-android\", currentLocation.getLatitude(),\n                currentLocation.getLongitude(), speed, \"speed\", \"km/h\", new Date().getTime());\n\n            SensorsInteractor sensorsInteractor = new SensorsMetricInteractor(metric);\n\n            new Thread(sensorsInteractor).start();\n        }\n    }\n```\n\nComo puede observarse, el envío de la métrica a la plataforma se realiza en otro hilo de ejecución.\nPreviamente, se ha populado la métrica con los datos de interés:\n\n* el identificador del dispositivo, `uniqueDeviceId`,\n* el ID de la aplicación, `sensors-android`,\n* las coordenadas en formato latitud y longitud,\n* el valor de la métrica, almacenado en la variable `speed`,\n* el tipo de métrica, en formato texto, `\"speed\"`,\n* las unidades de la métrica, en formato texto, `\"km/h\"`, y\n* un timestamp de la petición\n\nAdemás, se ha creado un objeto de tipo `Interactor` con dicha métrica para ser utilizado en segundo\nplano.\n\n## Pruebas\n\nUn requisito imprescindible en toda aplicación es la escritura de buenos tests. Se consideran como\nbuenas prácticas de escritura de tests las siguientes:\n\n* Un buen test tiene una única razón para fallar.\n* Un buen test verifica una única cosa del código a probar.\n* Un buen test evita la lógica condicional.\n* Un buen test tiene un nombre que describe de manera clara el caso de prueba. Por contra, no se puede\nconfiar en un test cuya implentación no tiene nada que ver con el nombre del método de test.\n* Un buen test crea/abre los recursos que necesita antes de su ejecución, y los cierra o borra al final\nde su ejecución.\n* Un buen test no define de manera explícita sus dependencias (*hardcoded dependencies*).\n* Un test que falla de manera constante es inútil.\n* Un test que no puede fallar no aporta valor, pues crea una falsa sensación de seguridad.\n\n### Tests unitarios\n\nPor tanto, y siguiendo las prácticas anteriores, para aquellas funcionalidades sin dependencias\nexternas se han escritos pruebas unitarias. Estos tests unitarios utilizan *jUnit* como framework de\nescritura y ejecución de tests.\n\nPara ejecutar los tests unitarios bastará con ejecutar desde el directorio raíz el comando:\n\n```shell\n    ./gradlew test\n```\n\nCuyo resultado será el siguiente:\n\n```shell\n    es.mdelapenya.uned.master.is.ubicomp.sensors.model.RangeTest \u003e testCompareThisGreaterThanThat PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.model.RangeTest \u003e testCompareThisLowerThanThat PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.model.RangeTest \u003e testCompareThisEqualsToThat PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.model.RangeTest \u003e testIsInRange PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.model.RangeTest \u003e testDefaultConstructor PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.util.SpeedConverterTest \u003e testConvert2 PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.util.SpeedConverterTest \u003e testConvert PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.util.UIManagerTest \u003e testSyncUIRequiredWithDifferentIds PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.util.UIManagerTest \u003e testSyncUIRequired PASSED\n    es.mdelapenya.uned.master.is.ubicomp.sensors.util.UIManagerTest \u003e testSyncUIRequiredWithDifferentSpeed PASSED\n```\n\n#### Tests sobre el modelo\n\nLa clase `RangeTest` recoge los tests unitarios sobre las operaciones con lógica de negocio de la clase\n`Range`. En particular los dos métodos en los que podrían producirse bugs o errores, puesto que los\ndemás métodos de la clase son `getters` o `setters` de los atributos, siendo éstos populados en los\nconstructores, evitando así que obtuvieran valores nulos.\n\nPara el método `compareTo`, resultado de implementar la clase `Comparable` de Java, se verifica que\npara todos los casos posibles se verifica que un objeto Range es mayor, menor o igual que otro. Este\nmétodo `compareTo` es internamente llamado por `Collections.sort(list)` para realizar ordenaciones\nsobre una lista, en este caso de objetos `Range`.\n\nPara el método `isInRange`, se verifica que dada una velocidad en kilómetros, ésta está dentro o fuera\n(tanto por encima como por debajo) del rango definidido por las velocidades mínima y máxima que lo\ndelimitan.\n\nPor último, al existir un constructor por defecto, se verifica que éste popula los atributos con los\nvalores por defecto.\n\n#### Tests sobre las utilidades\n\nLa clase `SpeedConverterTest` recoge los tests unitarios sobre las operaciones con lógica de negocio\nde la clase `SpeedConverter`. En particular del único método de la clase, `convertToKmsh`, que dada\nuna velocidad en metros por segundo, obtiene su equivalente en kilómetros por hora. De este modo, los\ntests verifican que para una velocidad de 1 metro por segundo la equivalencia es de 3.6 kilómetros\npor hora, y que para un valor arbitrario como es 13 metros por segundo, la equivalencia es de 46.8\nkilómetros por hora. En ambos casos, al tratarse de valores con decimales, el API de `jUnit` obliga\na definir un delta para determinar el margen de error producido por el cálculo de decimales.\n\nLa clase `UIManagerTest` recoge los tests unitarios sobre las operaciones con lógica de negocio\nde la clase `UIManager`. En particular del único método de la clase, `syncUIRequired`, que dados unos\nvalores de entrada, actuales y anteriores, para la velocidad y el nombre del rango, determina si es\nnecesario actualizar la interfaz de usuario o UI. De este modo, los tests verifican que el método\ndevuelve `true` si y sólo si alguno de los valores actuales es distinto a su contrapartida en los\nvalores anteriores.\n\n### Tests de integración\n\nDebido a la simplicidad de la aplicación, y de no poseer dependencias externas con las que integrarse,\nno se han escrito tests de ingración.\n\n### Exploratory Testing\n\nSe han realizado pruebas exploratorias (o `Exploratory Testing`) sobre la aplicación, de modo que\ntodos los controles han sido extensamente probados, utilizando diferentes prácticas: valores límite,\nvalores incorrectos, valores nulos en los campos, etc.\n\n## Manual de Uso\n\nA continuación se presentan las diferentes pantallas de la aplicación, con la información necesaria\npara utilizar la aplicación con fluídez.\n\n### Pantalla Principal\n\nAl abrir la aplicación se mostrará la actividad principal, que directamente mostrará dos etiquetas,\nla superior mostrando la velocidad en kilómetros por hora a la que se encuentra el dispositivo, y\notra etiqueta inmediatamente debajo mostrando el nombre del rango de velocidad en el que se encuentra\nla velocidad actual.\n\n![MainActivity](./static/screenshot_main.png)\n\n### Pantalla de Administración de Rangos\n\nPulsando en la barra de navegación de la aplicación, arriba a la izquierda, aparecerá el icono del\nmenú, representado por tres puntos verticales. Una vez pulsado, desplegará las opciones del menú, que\npara la aplicación corresponden con la gestión de los rangos del sensor, así como una opción mostrando\nuna ayuda de la aplicación.\n\n![Configuración desde MainActivity](./static/screenshot_main_config.png)\n\n### Pantalla de Lista de rangos\n\nAl pulsar en la opción \"Rangos del Sensor\" del menú, se mostrará la pantalla de gestión de rangos,\nformada por una lista con los rangos disponibles en la actualidad, así como una opción para añadir\nnuevos rangos. Esta opción de \"Nuevo Rango\" se encuentra ubicado abajo a la derecha, siguiendo los\npatrones de diseño de Google basados en `Material Design`. Pulsando sobre esta opción se accederá a\nla pantalla de creación de rango, que detallaremos más adelante.\n\nPulsando sobre un elemento de la lista se accederá a la pantalla de edición de rango, que detallaremos\nmás adelante.\n\nPara volver a la pantalla principal, en la barra de navegación aparece una flecha apuntando a la\nizquierda, representando la vuelta a la pantalla anterior del flujo.\n\n![Lista de Rangos](./static/screenshot_range_list.png)\n\n### Pantalla de Nuevo Rango\n\nPara crear un nuevo Rango podremos introducir tres valores: nombre del rango, valor mínimo del rango,\nen kilómetros por hora, y valor máximo del rango, también en kilómetros por hora.\n\nLa aplicación validará que no existe otro rango con los valores mínimo o máximo iguales a los\nintroducidos, por tanto no permitirá el solapamiento entre rangos, y una velocidad podrá pertenecer\na un único rango.\n\n![Nuevo Rango](./static/screenshot_new_range.png)\n\n### Pantalla de Edición de Rango\n\nPara modificar un Rango podremos modificar alguno de los tres valores: nombre del rango, valor mínimo\ndel rango, en kilómetros por hora, y valor máximo del rango, también en kilómetros por hora.\n\nDel mismo modo que en la pantalla de creación de rangos, la aplicación validará que no existe otro\nrango con los valores mínimo o máximo iguales a los introducidos, a excepción de tratarse del propio\nrango, por tanto no permitirá el solapamiento entre rangos, y una velocidad podrá pertenecer a un único\nrango.\n\n![Modificación de Rango](./static/screenshot_edit_range.png)\n\n## Instalación de la aplicación\n\nPara instalar la aplicación en un dispositivo Android, al no estar publicada en `Google Play` como app,\nserá necesario construirla desde los fuentes. Para ello bastará con realizar los siguientes pasos:\n\n1) Bajar el código, en un directorio con permisos de escritura, teniendo `git` instalado:\n\n```shell\n    git clone https://github.com/mdelapenya/uned-sensors.git\n```\n\n2) Tener instalado Java:\n\n```shell\n    java --version\n```\n\n3) Ejecutar el proceso de construcción:\n\n```shell\n    ./gradlew assemble\n```\n\n4) Copiar el empaquetado al terminal, mediante terminal con `adb` o usando un interfaz gráfico.\n\n```shell\n    adb push ./app/build/outputs/apk/app-debug.apk /sdcard\n```\n\nDonde `/sdcard` representa el almacenamiento interno del dispositivo Android. Para instalarlo será\nnecesario habilitar la instalación de aplicaciones de fuentes desconocidas.\n\n## Integración con otros sistemas\n\nEn el momento que estamos leyendo los datos del sensor GPS del dispositivo, podríamos en ese punto\ncrear cualquier tipo de comunicación con un sistema de terceros para enviar dichos datos, mediante\ninvocaciones a un servicio web, por ejemplo. Dicho servicio web podría recoger los datos de velocidad\ninstantánea, datos de ubicación, identificador del dispositivo, entre otros, para poder clasificar\n y/o agrupar los datos en otras operaciones de explotación de datos.\n\n## Recursos","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdelapenya%2Funed-sensors","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmdelapenya%2Funed-sensors","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdelapenya%2Funed-sensors/lists"}