Capítulo 1. Primeros pasos

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Hay un camino por delante,

Zarzas y espinas, luego se despeja.

Ya casi está. Paciencia.

Probablemente ya te habrás dado cuenta en de que el Internet de las Cosas puede ser vasto, difícil de manejar y muy difícil de domar. Para planificar el camino a seguir, primero tendremos que identificar un área problemática que abordar y luego crear una arquitectura a partir de la cual diseñar y construir nuestra solución IoT.

Empecemos con algunas preguntas clave para establecer una línea de base: ¿Qué problema intentas resolver? ¿Dónde empieza y dónde acaba? ¿Por qué requiere un ecosistema IoT? ¿Cómo funcionarán juntas todas las piezas para resolver este problema? ¿Qué resultado puedes esperar si todo funciona como está diseñado? Exploraremos cada una de estas preguntas en detalle, y por el camino construiremos una solución IoT integrada de extremo a extremo que satisfaga nuestras necesidades.

Lo que aprenderás en este capítulo

Para ayudarte a comprender realmente cómo puede y debe construirse un sistema IoT, profundizaré en algunos conceptos arquitectónicos básicos basados en las preguntas anteriores y utilizaré esto como base para cada actividad de programación. A partir de ahí, construirás una solución que aborde el problema capa por capa, añadiendo más funcionalidad a medida que trabajes en cada capítulo posterior.

Ni que decir tiene, por supuesto, que las herramientas de desarrollo adecuadas probablemente te ahorrarán tiempo y frustración, por no mencionar que te ayudarán con las pruebas, la validación y la implementación. Hay muchas herramientas y marcos de desarrollo excelentes, comerciales y de código abierto, disponibles para ayudarte.

Si has sido desarrollador durante algún tiempo, supongo que tienes tus propias preferencias específicas de entorno de desarrollo que mejor se adaptan a tu estilo y enfoque de programación. Yo, desde luego, tengo las mías, y aunque los ejemplos que presento se basarán en mi conjunto preferido de herramientas, mi objetivo en este capítulo no es especificar las que debes utilizar, sino ayudarte a impulsar el desarrollo de IoT de forma que puedas salir rápidamente y, con el tiempo, elegir tus propias herramientas para futuros proyectos de desarrollo.

Los conceptos que presento serán lo más importante; los lenguajes de programación, las herramientas (y sus respectivas versiones) y los métodos pueden cambiarse. Estos conceptos representan algunos de los fundamentos del desarrollo coherente de software: diseño del sistema, codificación y pruebas.

Definir tu sistema

Crear una declaración del problema es probablemente la parte más importante de este rompecabezas. Empecemos por redactar algo que sea razonablemente sencillo, pero que sea suficiente para abarcar una variedad de retos interesantes del IoT:

Quiero entender el ambiente de mi casa, cómo cambia con el tiempo, y hacer ajustes para mejorar el confort ahorrando dinero.

Parece bastante sencillo, pero se trata de un objetivo muy amplio. Podemos acotarlo definiendo las acciones y objetos clave en nuestro planteamiento del problema. Nuestro objetivo es aislar el qué, el por qué y el cómo. Veamos primero el qué y el por qué y luego identifiquemos cualquier acción o acciones que el diseño deba considerar como parte de este proceso.

Desmenuzar el problema

Los ejercicios de este libro se centrarán en construir una solución IoT que pueda ayudarte a comprender el entorno de tu casa y responder adecuadamente. Se supone que querrás saber lo que ocurre en tu casa (dentro de lo razonable) y tomar algún tipo de medida si está justificada (por ejemplo, encender el aire acondicionado si la temperatura es demasiado alta).

Esta parte de tu enfoque de diseño considera tres actividades clave:

Medir: Recoger datos

Definamos esto en términos de lo que se puede detectar, como la temperatura, la humedad, etc. Esto se centra en la captura y transmisión de telemetría( datos demedición ). La acción -o mejor dicho, la categoría de acción- se denominará recogida de datos e incluirá los siguientes elementos de datos (puedes añadir más posteriormente):

  • Temperatura

  • Humedad relativa

  • Presión barométrica

  • Rendimiento del sistema (métricas de utilización de CPU, memoria, almacenamiento)

Modelo: Determinar los cambios relevantes a partir de una línea de base dada
Para decidir qué datos son relevantes y si un cambio de valor es importante o no, necesitamos no sólo recopilar datos, sino también almacenar y tender series temporales de datos sobre los elementos que podemos percibir (como la temperatura, la humedad, etc., como se indica en la definición anterior). Esto se conoce normalmente como conversión de datos → información. Me referiré a esta categoría como gestión de datos.
Gestiona: Actúa
Estableceremos algunas reglas básicas para determinar si hemos cruzado algún umbral importante, lo que significa simplemente que enviaremos una señal a algo si se cruza un umbral que requiera algún tipo de acción (por ejemplo, subir o bajar un termostato). Esto se conoce normalmente como conversión información → conocimiento. Me referiré a esta categoría como activadores del sistema.

En mi curso universitario de IoT, hablo a menudo de Medir, Modelar y Gestionar. Para mí, representan los aspectos centrales de cualquier diseño de IoT que, en última instancia, conducen a la consecución de los objetivos o resultados empresariales especificados del sistema.

Definición de resultados relevantes

Ahora que sabemos qué pasos debemos dar, exploremos la parte del porqué de nuestro planteamiento del problema. Podemos resumirlo en los dos puntos siguientes:

  • Aumentar el confort: Idealmente, nos gustaría mantener una temperatura y humedad constantes en nuestro entorno vital. Las cosas se complican un poco más si tenemos en cuenta el número de habitaciones, cómo se utilizan, etc. Me refiero a esta categoría de acción como gestión de la configuración, y va de la mano tanto de la gestión de datos como de los activadores del sistema.

  • Ahorra dinero: Esto es un poco complicado. La forma más obvia de ahorrar dinero es no gastarlo. Como es probable que tengamos que destinar recursos económicos a calentar, enfriar o humidificar una zona determinada, queremos optimizar: ni demasiado (despilfarro), ni demasiado poco (podríamos acabar con las tuberías de agua congeladas en invierno). Dado que es posible que tengamos que hacer frente a cierta complejidad -incluidos los costes de los servicios públicos, los cambios estacionales, etc., así como todo lo relacionado con la gestión de la configuración-, probablemente necesitaremos algunos análisis más avanzados para gestionar estas cuestiones. Llamaré a esta acción categoría de análisis.

Seguramente te habrás dado cuenta de que cada paso de las secciones qué y por qué tiene un nombre de categoría de acción que ayudará al diseño de la solución una vez que pasemos al cómo. Como recordatorio, estas categorías son recopilación de datos, gestión de datos, activadores del sistema, gestión de la configuración y análisis. Profundizaremos en cada una de ellas como parte de nuestro enfoque de implementación.

Aunque el planteamiento del problema parece bastante banal a primera vista, resulta que las cosas que tendrás que hacer para abordar el problema son en realidad bastante comunes en muchos sistemas de IoT. Existe la necesidad de recopilar datos en su origen, almacenar y analizar esos datos, y actuar si algún indicador sugiere que hacerlo sería beneficioso. Una vez que definas tu arquitectura IoT y empieces a construir los componentes que la implementan -aunque será específica para este problema-, verás cómo puede aplicarse a muchas otras áreas problemáticas.

Echemos un vistazo rápido a un sencillo flujo de datos que representa este proceso de decisión; en el diagrama de flujo de datos representado en la Figura 1-1, cada categoría de acción aparece resaltada.

Simple IoT data flow
Figura 1-1. Flujo de datos IoT simple

La mayoría de los sistemas IoT requerirán al menos algunas de las cinco categorías de acción que he mencionado. Esto significa que podemos definir una arquitectura que las mapee en un diagrama de sistemas y, a continuación, empezar a crear componentes de software que implementen parte del sistema.

Aquí es donde empieza la diversión para nosotros los ingenieros, así que pongámonos en marcha con una definición de arquitectura que pueda soportar nuestro planteamiento del problema (y que, de hecho, sea reutilizable para otros).

Diseñar una solución

La organización, la estructura y la claridad son las señas de identidad de una buena arquitectura, pero un exceso puede dar lugar a un sistema rígido que no se adapte bien a las necesidades futuras. Y si intentamos establecer una arquitectura que satisfaga todas nuestras necesidades plausibles, ¡nunca acabaremos (o quizá ni siquiera empecemos)! Se trata de equilibrio, así que definamos la arquitectura teniendo en cuenta la flexibilidad futura, pero también mantengamos las cosas relativamente bien delimitadas. Esto te permitirá centrarte en llegar a una solución rápidamente, sin dejar de permitir actualizaciones en el futuro. Pero antes, hay algunos términos clave que deben definirse para ayudar a establecer una construcción arquitectónica de referencia sobre la que construir tu solución.

Como recordarás de la Figura P-1 del prefacio, los sistemas IoT suelen diseñarse teniendo en cuenta al menos dos (y a veces tres o más) niveles arquitectónicos. Esto permite separar la funcionalidad tanto física como lógicamente, lo que permite esquemas de implementación flexibles. Todo esto equivale a decir que los servicios en la nube que se ejecutan en el nivel de la nube pueden, técnicamente hablando, estar en cualquier parte del mundo, mientras que los dispositivos que se ejecutan en el nivel del perímetro deben estar en la misma ubicación que los sistemas físicos que se van a medir. Tal y como indica la Figura P-1, un ejemplo de este escalonamiento puede incluir un dispositivo limitado con sensores o actuadores que hable con un dispositivo pasarela, que a su vez hable con un servicio basado en la nube, y viceversa.

Puesto que necesitamos un lugar para implementar estas cinco categorías de funcionalidad, es importante identificar su ubicación dentro de la arquitectura, de modo que podamos tener algunas cosas ejecutándose cerca de donde está la acción, y otras ejecutándose en la nube, donde tú y yo podamos acceder (e incluso ajustar) la funcionalidad fácilmente. Recordando la arquitectura de los niveles del perímetro y de la nube del prefacio, veamos cómo asignar cada una de las categorías de acción del qué y el por qué a cada nivel:

  • Nivel de perímetro (dispositivos limitados y dispositivos de pasarela): Recogida de datos, gestión de datos, activación de dispositivos, gestión de la configuración y análisis

  • Cloud Tier (servicios en la nube): Gestión de datos, gestión de la configuración y análisis

¿Por qué el nivel de perímetro y el nivel de nube incluyen funciones similares? En parte por necesidad, pero también porque, bueno, podemos. Los límites técnicos y la separación de responsabilidades entre el perímetro y la nube son cada vez más difusos a medida que aumenta la potencia de cálculo y las necesidades empresariales dictan capacidades de cálculo y análisis "lo más cerca posible del perímetro". Por ejemplo, algunas decisiones autónomas pueden no requerir que los mensajes atraviesen Internet hasta la nube y vuelvan, ya que la capa de perímetro puede gestionarlos directamente (y debería hacerlo en algunos casos). Así que es importante tener en cuenta esta capacidad siempre y cuando sea razonable.

La Figura 1-2 muestra cómo el sencillo flujo de datos de la Figura 1-1 encaja en una arquitectura por niveles.

Notional IoT data flow between the Edge and Cloud Tiers
Figura 1-2. Flujo de datos IoT teórico entre los niveles de perímetro y nube

De nuevo, fíjate en que tenemos cierta responsabilidad compartida, ya que algunas de las categorías de acción se implementan en ambos niveles. Normalmente, la duplicación de esfuerzos es algo malo, pero en este caso, ¡puede ser una ventaja! La analítica puede utilizarse para determinar si debe enviarse una activación a un dispositivo en función de algunos ajustes básicos: por ejemplo, si la temperatura de tu casa supera los 30ºC, probablemente querrás activar inmediatamente el sistema de climatización y empezar a enfriar las cosas hasta, digamos, 22ºC. No hay necesidad de depender de un servicio remoto basado en la nube en el Nivel Nube para hacer esto, aunque sería útil notificar al Nivel Nube que esto está ocurriendo, y quizás almacenar algunos datos históricos para su posterior análisis.

Nuestra arquitectura está empezando a tomar forma. Ahora sólo necesitamos una forma de mapearla en un diagrama de sistemas para poder interactuar con el mundo físico (utilizando sensores y actuadores). También sería bueno estructurar las cosas dentro de la capa de perímetro para evitar exponer componentes a Internet innecesariamente. Esta funcionalidad puede implementarse como una aplicación que puede ejecutarse directamente en el dispositivo o en un ordenador portátil u otro sistema informático genérico con lógica de simulación que pueda emular el comportamiento de sensores y actuadores. Esto servirá de base para una de las dos aplicaciones que desarrollarás, a partir de este capítulo.

Puesto que en algún momento querrás acceder a Internet, tu diseño debe incluir una pasarela para gestionar esta necesidad y otras. Esta funcionalidad puede implementarse como parte de una segunda aplicación que empezarás a desarrollar en este capítulo. Esta aplicación se diseñará para ejecutarse en un dispositivo pasarela (o, de nuevo, en un ordenador portátil u otro sistema informático genérico). Tu Aplicación de Dispositivo Pasarela y tu Aplicación de Dispositivo Restringida constituirán el "perímetro" de tu diseño IoT, al que me referiré como el Nivel de Borde de tu arquitectura en adelante.

También querrás implementar servicios de análisis, capacidades de almacenamiento y gestores de eventos de forma que sean seguros pero accesibles desde tu dispositivo de acceso y también por seres humanos. Hay muchas formas de hacerlo, aunque me centraré en el uso de uno o más servicios en la nube para gran parte de esta funcionalidad.

La Figura 1-3 proporciona una nueva vista que te dará una idea más clara de lo que vas a construir y de cómo puedes empezar a incorporar las cinco categorías de acción que he mencionado. Representa, en recuadros grises, los servicios en la nube dentro del Nivel Nube y las dos aplicaciones dentro del Nivel Perímetro que contendrán la funcionalidad de tu dispositivo limitado y de tu dispositivo pasarela, respectivamente.

Notional IoT simplified logical architecture with Edge and Cloud Tiers
Figura 1-3. Arquitectura lógica simplificada de IoT con niveles de perímetro y nube

Profundicemos un poco más en cada uno de ellos:

Aplicación de Dispositivo Restringido (CDA)
Construirás esta aplicación de software para que se ejecute como parte de un dispositivo restringido simulado dentro de tu entorno de desarrollo, y proporcionará funciones de recogida de datos y activación del sistema. Se encargará de la interfaz entre los sensores del dispositivo (que leen datos del entorno) y los actuadores (que desencadenan acciones, como encender o apagar el sistema de climatización). También intervendrá en la acción cuando sea necesaria una actuación. Con el tiempo, se conectará a una biblioteca de comunicaciones para enviar mensajes a la aplicación del dispositivo pasarela y recibirlos de ella.
Aplicación del Dispositivo Pasarela (GDA)
Construirás esta aplicación de software para que se ejecute como parte de un dispositivo de pasarela simulado dentro de tu entorno de desarrollo, y proporcionará funciones de gestión de datos, análisis y gestión de la configuración. Su función principal es gestionar los datos y las conexiones entre el CDA y los servicios en la nube que existen dentro del Nivel Nube. Gestionará los datos localmente, según proceda, y a veces actuará enviando una orden al dispositivo restringido que desencadene una actuación. También gestionará algunos de los ajustes de configuración -es decir, los que representan rangos nominales para tu entorno- y realizará algunos análisis iniciales cuando se reciba nueva telemetría.
Servicios en la nube
Todas las aplicaciones y funcionalidades de los servicios en la nube suelen hacer gran parte del trabajo pesado de procesamiento y almacenamiento de datos, ya que teóricamente pueden escalar ad infinitum. Esto significa sencillamente que, si están bien diseñados, puedes añadir tantos dispositivos como quieras, almacenar tantos datos como desees y realizar un análisis en profundidad de esos datos -tendencias, máximos, mínimos, valores de configuración, etc.-, transmitiendo al mismo tiempo cualquier información relevante a un usuario final humano, y quizás incluso generando acciones de nivel de perímetro basadas en cualquier cruce de umbral(es) definido(s). Técnicamente, los servicios en la nube dentro de un entorno IoT pueden gestionar todas las categorías de acciones mencionadas anteriormente, con la excepción de la recopilación de datos (lo que significa que no realizan acciones de detección o actuación directamente). Construirás algunos servicios en la nube para manejar esta funcionalidad, pero en su mayoría utilizarás los servicios genéricos ya disponibles de algunos proveedores de servicios en la nube.

Poniéndolo todo junto en una arquitectura lógica detallada, la Figura 1-4 muestra cómo cada componente lógico principal dentro de nuestros dos niveles arquitectónicos interactúa con los demás componentes.

Notional IoT detailed logical architecture with Edge and Cloud Tiers
Figura 1-4. Arquitectura lógica detallada del IoT con niveles de perímetro y nube

Utilizaremos las Figuras 1-3 y 1-4 como arquitectura de referencia para todos los ejercicios de este libro.

Ahora que ya sabemos a qué nos enfrentamos, vamos a configurar nuestro entorno de desarrollo para empezar a lanzar código.

Configurar tu entorno de desarrollo y pruebas

Construir y desplegar código en diferentes sistemas operativos, configuraciones de hardware y sistemas de configuración no es un paseo. En los proyectos típicos de IoT, tenemos que lidiar no sólo con diferentes componentes de hardware, sino también con las innumerables formas de desarrollar y desplegar en estas plataformas, por no mencionar las diversas idiosincrasias de integración continua/despliegue continuo (CI/CD) de los distintos entornos de proveedores de servicios en la nube en los que solemos trabajar.

Con todos estos retos, ¿cómo podemos empezar? Lo primero es lo primero: ¿qué problema intentamos resolver? Como desarrollador, quieres implementar tu diseño IoT en código, probarlo, empaquetarlo en algo que pueda distribuirse fácilmente a uno o más sistemas, y desplegarlo de forma segura. Podemos pensar en nuestros retos de desarrollo en términos de fases de construcción, prueba e implementación, que también se corresponden con dos niveles arquitectónicos: Nivel de perímetro (generación de telemetría y actuación) y Nivel de nube (infraestructura informática remota). Más adelante, en la Parte IV, trataré la funcionalidad de la Nube, mientras que las Partes I, II y III se centrarán en el perímetro.

Aunque la capa de perímetro de un ecosistema IoT típico podría tener que tratar con hardware especializado, para nuestros propósitos, puedes simular gran parte del comportamiento requerido del sistema, e incluso emular algunos componentes de hardware dentro de tu entorno de desarrollo local. Esto facilitará mucho la implementación y es perfectamente adecuado para todos los ejercicios requeridos en este libro.

Hay muchas formas de ponerse en marcha con el desarrollo de IoT. Los ejercicios de este libro se centran en la creación de un enfoque de implementación simulada integrada, que se menciona a continuación. La integración del hardware es completamente opcional, y está fuera del alcance del libro, pero se mencionará muy brevemente como ejercicio opcional en el Capítulo 4.

Implementación simulada integrada
Este enfoque no requiere ningún dispositivo especializado y te permite utilizar tu estación de trabajo de desarrollo (portátil) como dispositivo de puerta de enlace y dispositivo restringido. Esto significa que ejecutarás tu GDA y CDA en tu entorno informático local. Emularás tu hardware de detección y actuación construyendo simuladores de software elementales para capturar esta funcionalidad dentro de tu CDA. Todos los ejercicios obligatorios del libro, a excepción de los ejercicios opcionales del Capítulo 4 publicados en línea, deberían poder implementarse utilizando este enfoque de implementación.
Implementación física separada

Esto requiere un dispositivo de hardware, como una Raspberry Pi, que te permita conectarte e interactuar con sensores y actuadores reales. Aunque muchos dispositivos informáticos de placa única (SBC) disponibles en el mercado pueden utilizarse como estaciones de trabajo informáticas completas, me referiré a éste como tu dispositivo restringido, y ejecutará tu CDA directamente en el dispositivo. Al igual que con el enfoque de implementación simulada integrada, ejecutarás el CDA en tu entorno informático local.

Nota

Como se indica en el prefacio, el documento RFC 7228 del IETF define varias clases de dispositivos restringidos (también denominados nodos restringidos). Estas clases incluyen la Clase 0 (muy restringida), la Clase 1 (restringida) y la Clase 2 (algo restringida).1 Para nuestros propósitos, en supondremos que nuestro CDA puede funcionar en dispositivos de Clase 2 o incluso más potentes, que suelen soportar pilas de red completas basadas en IP, lo que significa que los protocolos que trataremos en este libro funcionarán generalmente en este tipo de dispositivos. Aunque técnicamente es posible conectar dispositivos de Clase 2 directamente a Internet, todos los ejemplos y ejercicios interactuarán indirectamente con Internet a través del CDA.

Implementación física combinada
Este enfoque es casi idéntico al de la implementación separada, pero ejecutará tanto tu CDA como tu GDA en el dispositivo SBC. Esto significa técnicamente que puedes elegir implementar cada aplicación en un SBC diferente, aunque no es necesario para ninguno de los ejercicios mencionados.

Aunque los ejercicios del libro se centran en el enfoque de implementación simulada integrada, si eliges cualquiera de los dos últimos caminos para tu implementación, hay una amplia gama de SBC económicos que pueden servirte. Los únicos ejercicios de este libro que requieren hardware son los ejercicios opcionales del Capítulo 4, y aunque posiblemente podrías implementarlos en otras plataformas de hardware, están diseñados teniendo en cuenta el siguiente hardware: Raspberry Pi Modelo 3 o 4 y la placa Sense HAT (que se conecta a su entrada/salida de propósito general [GPIO] y utiliza el bus de Circuitos Integrados [I2C] para las comunicaciones entre dispositivos). Si seleccionas un dispositivo diferente para estos ejercicios, quizá quieras considerar uno que incluya lo siguiente Funcionalidad GPIO, I2C, conexión en red TCP/IP y UDP/IP mediante WiFi o Ethernet, compatibilidad con un sistema operativo basado en Linux (como Debian o un derivado), y compatibilidad con para tanto Python 3 como Java 11 (o superior).

Los ejercicios del libro se centrarán en la ruta de implementación simulada integrada. La Parte II introduce el concepto de integración con el mundo físico, y abordaré algunos conceptos de integración de hardware en el Capítulo 4, sin dejar de centrarme en los datos simulados y el hardware emulado.

Independientemente de la ruta de implementación seleccionada, todos los ejercicios y ejemplos suponen que realizarás el desarrollo y la implementación en una única estación de trabajo. Esto implica un proceso de tres pasos que incluye la preparación de tu entorno de desarrollo, la definición de tu estrategia de pruebas y la identificación de un enfoque de automatización de la compilación y la implementación. En este capítulo trataré los aspectos básicos de estos pasos para que puedas empezar, pero también los iré ampliando a medida que te adentres en ejercicios posteriores que tengan dependencias adicionales y necesidades de pruebas y automatización.

Nota

Puede que ya tengas experiencia desarrollando aplicaciones tanto en Java como en Python utilizando tu propio entorno de desarrollo. Si es así, asegúrate de revisar el primer paso -preparación de tu entorno de desarrollo- para asegurarte de que tu entorno de desarrollo, incluyendo tu sistema operativo, el tiempo de ejecución de Java y el intérprete de Python, son compatibles con los requisitos del ejercicio.

Paso I: Prepara tu entorno de desarrollo

Recuerda que tu CDA se escribirá en Python, y tu GDA en Java. Aunque técnicamente esto significa que cualquier SO que soporte Python 3.7 o superior y Java 11 o superior puede funcionar para la mayoría de los ejercicios, hay algunas dependencias específicas de Linux que debes conocer antes de configurar tu entorno de desarrollo:

  • Capítulo 4: El emulador de hardware del que hablaré en este capítulo requiere un entorno basado en Unix junto con un servidor X11 para soportar su interfaz gráfica de usuario (GUI). Linux y macOS deberían funcionar, mientras que Windows requerirá Windows Subsystem for Linux (WSL)2 además de un servidor X11.

  • Capítulo 8: El servidor CoAP basado en Python3 es un ejercicio opcional y se ha probado parcialmente con el cliente Java del Capítulo 9 en Windows 10, macOS y Linux. En el momento de escribir esto, probablemente tendrás que ejecutar estas pruebas en un entorno basado en Linux.

  • Capítulo 9: Algunos de los ejercicios de cliente CoAP basados en Python dependen actualmente de bindings específicos de Linux. En el momento de escribir esto, probablemente tendrás que ejecutar estas pruebas en un entorno basado en Linux.

Aunque hablaré de la configuración para Linux, macOS y Windows, te sugiero que utilices Linux como entorno de desarrollo para evitar algunos de los problemas de integración que acabo de mencionar. Asegúrate de leer PIOT-CFG-01-001 para obtener más información sobre la configuración del entorno operativo y las consideraciones de compatibilidad de las bibliotecas. Muchas de las bibliotecas de código abierto de las que dependen los ejercicios se mantienen activamente; sin embargo, esto no es así en todos los casos. Asegúrate de comprobar cada biblioteca para conocer la última versión probada, su licencia y uso, y sus limitaciones de compatibilidad con el entorno operativo.

Nota

La mayor parte de mi propio desarrollo se realiza en Windows 10, y la mayor parte de la ejecución de la aplicación y las pruebas se realizan utilizando WSL con un núcleo Ubuntu 20.04LTS. Si tienes que utilizar un entorno operativo que no sea Linux, puedes construir tu solución IoT de extremo a extremo omitiendo los ejercicios de los capítulos 4, 8 y 9, y basándote en los simuladores de datos descritos en el capítulo 3 y en el protocolo MQTT4 para la conectividad. No obstante, asegúrate de leerlos todos, ya que cada uno de ellos te proporcionará más información sobre cómo hacer evolucionar tus aplicaciones en el futuro.

Puedes asegurarte de que tu estación de trabajo tiene instalado el material adecuado para admitir estos idiomas y sus dependencias asociadas siguiendo estos pasos:

  1. Asegúrate de que Python 3.7 o superior está instalado en tu estación de trabajo (la última versión en el momento de escribir esto es la 3.10, aunque mi desarrollo y pruebas originales se hicieron principalmente dentro de WSL utilizando Python 3.8.5). Para comprobar si ya está instalado, haz lo siguiente:

    1. Abre una ventana de terminal o consola y escribe lo siguiente (asegúrate de utilizar dos guiones antes del parámetro)

      1. Linux/macOS:

        $ python3 –-version
      2. Ventanas:

        C:\programmingtheiot> python –-version
    2. Debería devolver una salida similar a la siguiente:

      Python 3.8.5
    3. Si la versión devuelta es inferior a 3.7, o si recibes un error (por ejemplo, "no encontrado"), tendrás que instalar Python 3.7 o superior. Sigue las instrucciones para tu sistema operativo (Windows, macOS, Linux) en https://www.python.org/downloads.

      Nota

      En algunos casos, puede que necesites descargar el código fuente de Python y luego compilar e instalar los ejecutables. Consulta las instrucciones en https://devguide.python.org/setup/ si necesitas seguir este camino. Ten en cuenta que este proceso puede llevar un tiempo.

  2. Asegúrate de que pip está instalado en tu estación de trabajo. Si no es así, puedes instalar pip descargando el script de arranque e instalación. Si utilizas WSL o Ubuntu, puede que tengas que instalar pip utilizando el gestor de paquetes apt.

    1. Abre una ventana de terminal o de consola y escribe lo siguiente (utilizando de nuevo dos guiones antes del parámetro):

      $ pip –-version
    2. Debería devolver una salida similar a la siguiente:

      pip 21.0.1
    3. Si pip no está instalado, o si tu versión no está actualizada, utiliza Python para ejecutar el script de instalación de pip. Escribe el siguiente comando:

      1. Linux/macOS:

        $ python3 get-pip.py
      2. Ventanas:

        C:\programmingtheiot> python get-pip.py
  3. Asegúrate de que Java 11 o superior está instalado en tu estación de trabajo (la última versión de OpenJDK en el momento de escribir esto es JDK 18). Puedes comprobar si ya está instalado, o instalarlo si no lo está, siguiendo estos pasos:

    1. Abre una ventana de terminal o consola y escribe lo siguiente (hay dos guiones antes del parámetro, aunque es probable que funcione con uno solo):

      $ java –-version
    2. Debería devolver algo como lo siguiente (asegúrate de que es al menos Java 11):

      openjdk 14.0.2 2020-07-14
      OpenJDK Runtime Environment (build 14.0.2+12-Ubuntu-120.04)
      OpenJDK 64-Bit Server VM (build 14.0.2+12-Ubuntu-120.04, mixed mode,
      sharing)
    3. Si aparece un error (por ejemplo, "no encontrado"), tendrás que instalar Java 11 o superior. Sigue las instrucciones para tu plataforma (Windows, macOS o Linux) en el sitio web de OpenJDK.

  4. Asegúrate de que Git está instalado en tu estación de trabajo. Si no es así, puedes instalar Git fácilmente. Ve a "Instalar Git" y revisa las instrucciones para tu sistema operativo específico.

    Un requisito previo para cualquiera de los ejercicios de este libro, y para configurar tu entorno de desarrollo, es tener conocimientos básicos de Git, una herramienta de gestión y versionado de código fuente. Muchos IDE vienen con la gestión del código fuente ya activada a través de un cliente Git integrado. En un paso anterior, instalaste Git a través de la línea de comandos para poder ejecutar comandos Git independientemente de tu IDE. Para más información sobre el uso de Git desde la línea de comandos, consulta la documentación del tutorial de Git.

    Consejo

    Puedes utilizar Git como herramienta independiente de gestión del código fuente en tu estación de trabajo de desarrollo local y gestionar tu código fuente en la nube utilizando una variedad de servicios gratuitos y comerciales. GitHub5 es el servicio que utilizo para alojar los repositorios de código y las últimas instrucciones de los ejercicios (que también incorporan muchas de las soluciones). Asegúrate de seguir el tablero Kanban del libro mientras realizas cada ejercicio de este libro, ya que contendrá la información más reciente.

  5. Crea un directorio de desarrollo de trabajo y descarga el código fuente y las pruebas unitarias de este libro:

    1. Abre una ventana de terminal o de consola, crea un nuevo directorio de trabajo de desarrollo y, a continuación, cambia a ese directorio. A continuación, escribe lo siguiente

      1. Linux/macOS:

        mkdir $HOME/programmingtheiot
        cd $HOME/programmingtheiot
      2. Ventanas:

        mkdir C:\programmingtheiot
        cd C:\programmingtheiot
    2. Clona los dos siguientes repositorios de código fuente de este libro escribiendo lo siguiente (también puedes clonar simplemente los repositorios desde el IDE):

      $ git clone https://github.com/programmingtheiot/python-components.git
      $ git clone https://github.com/programmingtheiot/java-components.git
  6. Configura tu entorno Python. Suele ser más fácil, aunque no obligatorio, utilizar un entorno virtual para aislar tus dependencias y bibliotecas de Python.

    Hay un puñado de formas de establecer un entorno de ejecución virtual para Python en tu sistema, y mi objetivo en este paso no es discutirlas todas. Python 3.3 o superior proporciona un módulo de entorno virtual, por lo que no tienes que instalar virtualenv a menos que ese sea tu enfoque preferido para la virtualización de Python. Puedes leer más sobre el uso del módulo venv en https://docs.python.org/3/library/venv.html.

    1. Crea un entorno virtual de Python. Abre un terminal o una ventana de consola, cambia el directorio a la ruta de instalación del entorno virtual que desees (por ejemplo, $HOME/programaciontheiot/piotvenv, aunque puedes elegir cualquier directorio que desees), y crea un entorno virtual (venv) de la siguiente manera:

      1. Linux/macOS:

        $ python3 -m venv $HOME/programmingtheiot/piotvenv
      2. Ventanas:

        C:\programmingtheiot> python -m venv C:\programmingtheiot\piotvenv
    2. Instala los módulos de Python necesarios. Puedes hacerlo escribiendo lo siguiente:

      1. Linux/macOS:

        $ cd $HOME/programmingtheiot
        $ . piotvenv/bin/activate
        (piotvenv) $ pip install -r ./python-components/requirements.txt
      2. Ventanas:

        cd C:\programmingtheiot
        C:\programmingtheiot> piotvenv\Scripts\activate.bat
        (piotvenv) C:\programmingtheiot>
        pip install -r .\python-components\requirements.txt
    3. Asegúrate de que tu virtualenv puede activarse. Puedes activate (utilizando el script activar) y luego deactivate virtualenv (utilizando el comando desactivar) desde tu línea de comandos con bastante facilidad:

      1. Linux/macOS:

        $ . piotvenv/bin/activate
        (piotvenv) $ deactivate
      2. Ventanas:

        C:\programmingtheiot> piotvenv\Scripts\activate.bat
        (piotvenv) C:\programmingtheiot> deactivate

Llegados a este punto, tu estación de trabajo de desarrollo está configurada en su mayor parte. El siguiente paso es configurar tu entorno de desarrollo y clonar el código fuente de muestra del libro.

Configurar un entorno de desarrollo integrado (IDE)

Hay muchas herramientas e IDE excelentes que te ayudan a ti, el desarrollador, a escribir, probar e implementar aplicaciones escritas tanto en Java como en Python. Hay herramientas con las que estoy muy familiarizado y que funcionan bien para mis necesidades de desarrollo. Supongo que tú eres muy parecido y tienes tus propias preferencias de herramientas. En realidad, no importa qué conjunto de herramientas utilices, siempre que las herramientas cumplan algunos requisitos básicos. Para mí, éstas incluyen resaltado y completado de código, formateo y refactorización de código, depuración, compilación y empaquetado, pruebas unitarias y de otro tipo, y control del código fuente.

He desarrollado los ejemplos de este libro utilizando el IDE Eclipse con PyDev instalado, ya que cumple los requisitos que he especificado y proporciona un montón de otras funciones prácticas que utilizo habitualmente en mis proyectos de desarrollo. Puede que conozcas otros IDE, como Visual Studio Code e IntelliJ IDEA, que también son compatibles con Java y Python. Por supuesto, la elección del IDE para los ejercicios de este libro depende totalmente de ti.

Si ya estás familiarizado con la escritura, las pruebas y la gestión de aplicaciones de software utilizando otro IDE, la mayor parte de esta sección te resultará obvia. Sin embargo, te recomiendo que la leas, ya que esta sección sienta las bases para el desarrollo de tu GDA y CDA.

Configura tu proyecto de Aplicación Dispositivo Pasarela

El primer paso en este proceso es instalar la última versión de Eclipse IDE para el desarrollo Java. Puedes encontrar los últimos enlaces de descarga de Eclipse en https://www.eclipse.org/downloads. Te darás cuenta de que hay muchos sabores diferentes del IDE disponibles. Para nuestros propósitos, puedes elegir simplemente "Eclipse IDE para desarrolladores Java". A continuación, sigue las instrucciones para instalar el IDE en tu sistema local.

Una vez instalado, inicia Eclipse, selecciona Archivo → Importar, busca Git → "Proyectos desde Git" y haz clic en Siguiente.

Selecciona "Repositorio local existente" y haz clic en Siguiente. Si ya tienes algunos repositorios Git en tu ruta de inicio, Eclipse probablemente los recogerá y los presentará como opciones a importar en el siguiente diálogo (no se muestra). Para importar el repositorio recién clonado, haz clic en Añadir, lo que te llevará al siguiente diálogo, mostrado en la Figura 1-5. Desde aquí, puedes añadir tu nuevo repositorio Git.

En mi estación de trabajo, el repositorio que quiero importar se encuentra en E:\aking\programmingtheiot\java-components. Lo más probable es que el tuyo tenga un nombre diferente, ¡así que asegúrate de introducirlo correctamente! Para los ejemplos de Windows, me ceñiré principalmente a la ruta C:\programmingtheiot.

Import java-components from your local Git repository
Figura 1-5. Importar java-components desde tu repositorio Git local

Haz clic en Finalizar, y verás tu nuevo repositorio añadido a la lista de repositorios que puedes importar. Resalta este nuevo repositorio y haz clic en Siguiente. A continuación, Eclipse te presentará otro cuadro de diálogo y te pedirá que importes el proyecto utilizando una de varias opciones, como se muestra en la Figura 1-6.

Import java-components as an existing Eclipse project
Figura 1-6. Importar java-components como un proyecto Eclipse existente

Ahora tienes una opción: puedes importar java-components como un proyecto Eclipse existente utilizando el asistente para nuevos proyectos, o como un proyecto general. A menos que quieras personalizar completamente el entorno de tu proyecto, te recomiendo la primera opción: importar un proyecto Eclipse existente. Este proceso buscará un archivo .project en el directorio de trabajo (que he incluido en cada uno de los repositorios que ya has clonado), dando como resultado un nuevo proyecto Java llamado java-components. Si prefieres crear tu propio proyecto, puedes eliminarlo e importarlo como un nuevo proyecto utilizando el asistente correspondiente.

Haz clic en Finalizar, y verás que tu nuevo proyecto se añade a la lista de proyectos del Explorador de paquetes de Eclipse, que por defecto debería estar en la parte izquierda de la pantalla de tu IDE. Si no ves inmediatamente tu proyecto en el Explorador de paquetes, simplemente cambia la perspectiva del IDE a "Java" (o "Python" para el CDA).

Tu proyecto GDA ya está configurado en Eclipse, así que vamos a explorar los archivos que contiene. Navega hasta este proyecto en Eclipse y haz clic en el símbolo de intercalación (>) para expandirlo más, como se muestra en la Figura 1-7.

GDA project now set up and ready for use
Figura 1-7. Proyecto GDA configurado y listo para usar
Nota

¿Y si no te gusta el nombre del proyecto? No hay problema: puedes hacer clic con el botón derecho del ratón en el nombre java-components, seleccionar Cambiar nombre, escribir el nuevo nombre y hacer clic en Aceptar. Que sepas que seguiré refiriéndome al proyecto por el nombre original a lo largo del libro :).

Observarás que ya hay varios archivos incluidos en el proyecto: uno es GatewayDeviceApp en el paquete programmingtheiot.gda.app, y el otro, en el nivel superior, se llama pom.xml. El GatewayDeviceApp es un marcador de posición para empezar, aunque puedes sustituirlo por el tuyo propio. Sin embargo, te recomiendo que mantengas la misma convención de nombres, ya que el pom. xml depende de él para compilar, probar y empaquetar el código. Si ya te manejas bien con Maven, no dudes en hacer los cambios que quieras.

Ten en cuenta que si piensas construir tu GDA desde la línea de comandos, tendrás que instalar Maven si no está ya instalado en tu entorno.

Consejo

Para los que estéis menos familiarizados con Maven, el pom.xml es el principal archivo de configuración de Maven y contiene instrucciones para cargar dependencias, sus respectivas versiones, convenciones de nomenclatura para tu aplicación, instrucciones de compilación y, por supuesto, instrucciones de empaquetado. La mayoría de estas dependencias ya están incluidas, aunque puedes añadir las tuyas propias si encuentras otras que te resulten útiles. También verás que Maven tiene su propia estructura de directorios por defecto, que he mantenido para el repositorio Java. Para saber más sobre éstas y otras funciones de Maven, te recomiendo que sigas el tutorial de cinco minutos sobre Maven.

Ahora, para asegurarte de que tu entorno de desarrollo GDA está configurado correctamente, puedes construir y ejecutar el GDA desde el IDE (esto se ha probado en Eclipse). Simplemente haz lo siguiente

  1. Asegúrate de que tu puesto de trabajo está conectado a Internet.

  2. Ejecuta tu aplicación GDA dentro de Eclipse.

    1. Vuelve a hacer clic con el botón derecho del ratón en el proyecto java-components y desplázate hasta "Ejecutar como", y esta vez haz clic en "Aplicación Java".

    2. Comprueba la salida en la consola de la parte inferior de la pantalla de Eclipse IDE. La salida es similar a la siguiente:

      Jul 04, 2020 3:10:49 PM programmingtheiot.gda.app.GatewayDeviceApp
      initConfig INFO: Attempting to load configuration.
      Jul 04, 2020 3:10:49 PM programmingtheiot.gda.app.GatewayDeviceApp
      startApp INFO: Starting GDA...
      Jul 04, 2020 3:10:49 PM programmingtheiot.gda.app.GatewayDeviceApp
      startApp INFO: GDA ran successfully.

Si decides compilar el GDA y ejecutarlo desde la línea de comandos, tendrás que decirle a Maven que se salte las pruebas, ya que fallarán (puesto que todavía no hay ninguna implementación que probar). En un shell Linux, puedes utilizar el siguiente comando desde el directorio fuente de nivel superior de tu GDA:

$ mvn install -DskipTests

Llegados a este punto, estás preparado para empezar a escribir tu propio código para el CDA. Ahora vamos a configurar tu estación de trabajo de desarrollo para el CDA.

Configura tu proyecto de Aplicación de Dispositivo Restringido

Este proceso imitará el proceso de configuración de GDA, pero requiere añadir PyDev a Eclipse. Aquí tienes un resumen de actividades para empezar.

Si aún no se está ejecutando, inicia el IDE de Eclipse. En una ventana o pantalla aparte, abre tu navegador web y navega hasta la página de descarga de PyDev Python IDE para Eclipse; arrastra el icono "Instalar" de PyDev desde la página web y suéltalo cerca de la parte superior del IDE de Eclipse (verás que aparece un icono "más" verde, que es el indicador de que puedes soltarlo en el IDE). Eclipse instalará automáticamente PyDev y sus dependencias.

Una vez instalado PyDev, puedes cambiar el intérprete de Python para que utilice el entorno venv (o virtualenv) si elegiste crearlo en la sección anterior. Selecciona Preferencias → PyDev → Intérpretes → "Intérprete de Python". Eclipse presentará un diálogo similar al que se muestra en la Figura 1-8.

Add a new Python interpreter
Figura 1-8. Añadir un nuevo intérprete de Python

A continuación, añade un nuevo intérprete utilizando la selección "Buscar python/pypy.exe" y proporciona la información pertinente en la siguiente ventana emergente. Una vez completado, selecciona el intérprete venv (o virtualenv) y haz clic en Arriba hasta que esté al principio de la lista. En este momento, venv (o virtualenv) será tu intérprete de Python por defecto, como indica la Figura 1-9.

Haz clic en "Aplicar y cerrar".

Virtualenv Python interpreter now set as default
Figura 1-9. El intérprete de Python de Virtualenv ahora está configurado por defecto

Una vez completados estos pasos, selecciona Archivo → Importar e importa el repositorio Git python-components que ya has clonado desde GitHub. De nuevo, esto es casi idéntico a los pasos anteriores mostrados en las Figuras 1-5, 1-6 y 1-7, excepto que importarás el repositorio Git python-components que clonaste de GitHub.

En mi estación de trabajo, el repositorio que quiero importar se encuentra en:

C:\programmingtheiot\python-components

Al igual que con el GDA, es probable que el nombre de tu repositorio sea diferente, así que asegúrate de utilizar la ruta correcta. También he incluido el archivo Eclipse .project dentro de este repositorio, para que puedas importarlo como un proyecto Eclipse. Éste será por defecto Python, por lo que utilizará PyDev como plantilla de proyecto. De nuevo, puedes importarlo como quieras, pero mi recomendación es que lo importes como hiciste con el GDA.

Una vez que completes el proceso de importación, verás un nuevo proyecto en tu Explorador de Paquetes llamado python-components. Ahora tienes los componentes CDA configurados en tu IDE Eclipse.

Para ver los archivos que contiene, navega hasta python-components y haz clic en el signo de intercalación (>) para expandirlo más, como se muestra en la Figura 1-10.

CDA project now set up and ready for use
Figura 1-10. Proyecto CDA configurado y listo para usar

Observarás que ya hay muchos archivos Python incluidos en el proyecto, uno de los cuales es ConstrainedDeviceApp.py en el paquete programmingtheiot.cda.app, que es la envoltura de la aplicación para el CDA. También hay archivos __init__.py en cada paquete; son archivos vacíos que el intérprete de Python utiliza para determinar en qué directorios debe buscar los archivos Python (puedes ignorarlos por ahora). Al igual que el ejemplo de GDA dado anteriormente (y escrito en Java), el ConstrainedDeviceApp es simplemente un marcador de posición para que empieces.

También hay dos archivos .txt: requisitos.txt e importaciones_básicas.txt. Deberían ser los mismos - había creado el segundo para desambiguar con otros archivos de requisitos que no se utilizan para esta versión del libro. El archivo requirements. txt se utilizará para instalar las dependencias de las bibliotecas necesarias para soportar los próximos ejercicios de programación CDA.

Advertencia

Si has trabajado mucho con Python, probablemente estés familiarizado con la variable de entorno PYTHONPATH. Como he intentado mantener similar el esquema de empaquetado de GDA y CDA, puede que necesites decirle a PyDev (y a tu entorno virtualenv) cómo navegar por esta estructura de directorios para ejecutar tu aplicación. Asegúrate de que las rutas src/main/python y src/test/python están ambas configuradas en PYTHONPATH haciendo lo siguiente: haz clic con el botón derecho del ratón en "python-components", selecciona "PyDev - PYTHONPATH" y, a continuación, haz clic en "Add source folder", como se muestra en la Figura 1-11. Selecciona la carpeta python en main y haz clic en Aplicar. Haz lo mismo con la carpeta python de test. Haz clic en "Aplicar y cerrar" para terminar.

Updating the PYTHONPATH environment variable within PyDev and Eclipse
Figura 1-11. Actualización de la variable de entorno PYTHONPATH en PyDev y Eclipse

Ejecuta tu aplicación CDA dentro de Eclipse.

  1. Vuelve a hacer clic con el botón derecho del ratón en sobre el proyecto "python-components", desplázate hasta "Ejecutar como" y esta vez haz clic en "Ejecutar Python".

  2. Comprueba la salida en la consola de la parte inferior de la pantalla del IDE de Eclipse. Al igual que en la ejecución de la prueba GDA, no debería haber errores, y la salida debería ser similar a la siguiente:

    2020-07-06 17:15:39,654:INFO:Attempting to load configuration...
    2020-07-06 17:15:39,655:INFO:Starting CDA...
    2020-07-06 17:15:39,655:INFO:CDA ran successfully.

Configuración de la aplicación

Después de ejecutar el CDA y el GDA, habrás probablemente notado los mensajes de registro relacionados con la configuración, y por supuesto recordarás la discusión sobre la gestión de la configuración anterior en este capítulo. Como tendrás que tratar con varios parámetros de configuración diferentes para cada aplicación, he proporcionado una clase de utilidad básica dentro de cada repositorio de código para ayudarte con esto: se llama ConfigUtil.

En Python, ConfigUtil delega en su módulo incorporado configparser y en Java, ConfigUtil delega en la biblioteca commons-configuration de Apache. Ambas te permitirán cargar el archivo de configuración por defecto (./config/PiotConfig.props) o una versión personalizada.

La forma más sencilla de garantizar que el CDA y el GDA cargan correctamente el archivo de configuración por defecto de cada repositorio es actualizar la propiedad DEFAULT_CONFIG_FILE_NAME config/PiotConfig.props' para que haga referencia al nombre de archivo completo (absoluto) de PiotConfig.props en la clase ConfigConst de cada repositorio (que se encuentra en el directorio ./programmingtheiot/common de cada repositorio).

Aquí tienes un ejemplo específico de Windows de la propiedad DEFAULT_CONFIG_FILE_NAME actualizada del CDA tras la modificación (tendrás que cambiarla para asignarla a tu propio archivo y utilizar una convención de nomenclatura de rutas diferente si utilizas WSL o Linux, por supuesto):

DEFAULT_CONFIG_FILE_NAME = "C:\programmingtheiot\python-components\config\
  PiotConfig.props"

Para los ejercicios de este libro, muchas de las "consts" que necesitarás ya están definidas en la clase ConfigConst de cada repositorio, aunque puedes (y probablemente necesitarás) añadir las tuyas propias.

El formato del archivo de configuración es el mismo tanto para el CDA como para el GDA. Aquí tienes una breve muestra del PiotConfig.props del CDA:

[ConstrainedDevice]
deviceLocationID = constraineddevice001
enableEmulator   = False
enableSenseHAT   = False
enableMqttClient = True
enableCoapClient = False
enableLogging    = True
pollCycleSecs    = 60
testGdaDataPath  = /tmp/gda-data
testCdaDataPath  = /tmp/cda-data

Y aquí tienes un fragmento del PiotConfig.props de la GDA:

[GatewayDevice]
deviceLocationID        = gatewaydevice001
enableLogging           = True
pollCycleSecs           = 60
enableMqttClient        = True
enableCoapServer        = False
enableCloudClient       = False
enableSmtpClient        = False
enablePersistenceClient = False
testGdaDataPath         = /tmp/gda-data
testCdaDataPath         = /tmp/cda-data

Observa que las secciones se designan mediante una palabra clave contenida entre corchetes, y las propiedades tienen formato clave = valor. Esto facilita añadir nuevas secciones y pares clave/valor por igual.

Un caso especial que se aborda en cada implementación de ConfigUtil es la posibilidad de definir -y cargar- un archivo de configuración independiente que contenga credenciales u otros datos sensibles que no deben formar parte de la configuración de tu repositorio. Cada sección te permite especificar un valor para credFile, que es una clave que mapea a un archivo local que puede y debe estar fuera de tu repositorio.

Advertencia

Si echas un vistazo a PiotConfig.props para el CDA y el GDA, probablemente te darás cuenta de que contiene una entrada credFile para algunas secciones. La razón de esto es trasladar las referencias a contraseñas, tokens de autenticación de usuarios, claves API, etc. fuera del archivo de configuración principal para que se pueda hacer referencia a ellos por separado. Es muy importante mantener secretos como estos fuera de tus repositorios: NUNCA debes introducir nombres de usuario, contraseñas, claves privadas o cualquier otro dato sensible en tu repositorio Git. Si necesitas una forma de almacenar este tipo de información, puedes leer detenidamente el artículo "Secretos encriptados " para saber más sobre este proceso en GitHub. El almacenamiento seguro de credenciales es un tema importante, pero que queda fuera del alcance de este libro.

El enfoque de configuración que he descrito aquí es bastante básico y está diseñado sólo para fines de prueba y creación de prototipos. Si llevas tiempo programando, es posible que ya tengas una estrategia y una solución de configuración de la aplicación. Siéntete libre de adaptar la funcionalidad de configuración que he presentado aquí a tus necesidades específicas.

Llegados a este punto, tanto tu GDA como tu CDA deberían estar configurados y funcionando dentro de tu IDE, y deberías estar familiarizado con el funcionamiento de la lógica de configuración. ¡Ahora estás listo para empezar a escribir tu propio código para ambas aplicaciones!

Sin embargo, antes de pasar a los ejercicios del Capítulo 2, hay dos temas más que debemos tratar: las pruebas y la automatización.

Paso II: Define tu estrategia de pruebas

Ahora que tu entorno de desarrollo está establecido para tu GDA y CDA, podemos hablar de cómo probarás el código que vas a desarrollar. Obviamente, unas buenas pruebas son una parte de vital importancia de cualquier esfuerzo de ingeniería, y la programación no es una excepción. Cada aplicación que construyas debe probarse a fondo, tanto si funciona de forma totalmente independiente de otras aplicaciones como si está estrechamente integrada con otros sistemas. Además, cada unidad de código que escribas debe probarse para garantizar que se comporta como se espera de ella. ¿Qué es exactamente una unidad? A nuestros efectos, una unidad siempre se representará como una función o método que quieras probar.

Consejo

¿Cuál es la diferencia entre una función y un método? Simplificando mucho, una función es una agrupación de código con nombre que realiza una tarea (como sumar dos números) y devuelve un resultado. Si la función acepta alguna entrada, se le pasará como uno o varios parámetros. Un método es casi idéntico a una función, pero está unido a un objeto. En el lenguaje orientado a objetos, un objeto no es más que una clase que se ha instanciado, y una clase es la definición formal de un componente: sus métodos, parámetros, lógica de construcción y deconstrucción. Todos los ejemplos Java de este libro se representarán en forma de clase, con métodos definidos como parte de cada clase. Python puede escribirse en forma de script con funciones o como clases con métodos, pero yo prefiero escribir clases Python con métodos y así lo haré para cada ejemplo Python mostrado en este libro, con sólo unas pocas excepciones.

Pruebas unitarias, de integración y de rendimiento

Hay muchas formas de probar aplicaciones y sistemas de software, y existen excelentes libros, artículos y blogs sobre el tema. Desarrollar una solución IoT que funcione requiere prestar mucha atención a las pruebas, dentro de una aplicación y entre diferentes aplicaciones y sistemas. Para la solución que vas a desarrollar, me centraré sólo en tres: pruebas unitarias, pruebas de integración y pruebas de rendimiento.

Las pruebas unitarias son módulos de código escritos para probar la unidad más pequeña posible de código accesible a la prueba, como una función o un método. Estas pruebas se escriben para verificar que un conjunto de entradas a una determinada función o método devuelve un resultado esperado. A menudo también se prueban las condiciones límite, para garantizar que la función o el método pueden manejar adecuadamente este tipo de condiciones.

Consejo

Técnicamente, una unidad de código puede ser una sola línea, varias líneas de código o incluso una biblioteca de código entera. Para nuestros propósitos, una unidad se refiere a una o más líneas de código, o a toda una biblioteca de código, a la que se puede acceder a través de una única interfaz que está disponible en el sistema local, es decir, una función o un método que encapsula la funcionalidad de la unidad y que se puede llamar desde tu aplicación de prueba. Esta funcionalidad puede ser, por ejemplo, un algoritmo de ordenación, un cálculo, o incluso un punto de entrada a una o más funciones o métodos adicionales.

Utilizo JUnit para las pruebas unitarias de código Java (incluido en Eclipse), y el framework unittest de Python6 de Python (forma parte del intérprete estándar de Python y está disponible en PyDev). No tienes que instalar ningún componente adicional para escribir y ejecutar pruebas unitarias dentro de tu IDE si utilizas Eclipse y PyDev.

Nota

En tu proyecto CDA, es probable que hayas observado dos estructuras de directorios para tu código fuente: una para el código fuente Java, ubicada en . /src/main/java, y otra para el código de pruebas unitarias Java, ubicada en ./src/test/java. Esta es la convención por defecto para los proyectos Maven, así que he optado por utilizar la misma convención de nomenclatura de directorios también para el CDA (cambiando "java" por "python", por supuesto).

Habrás observado que los proyectos CDA y GDA contienen un directorio . /src/test/python y un directorio ./src/test/java, respectivamente. Te proporciono la mayoría de las pruebas unitarias y muchas pruebas de integración para que compruebes si tu implementación funciona, desglosadas por cada capítulo. Te servirán para los ejercicios básicos, aunque no pretenden cubrir todos los perímetros posibles. Para una cobertura de pruebas adicional, y para todos los ejercicios opcionales, tendrás que crear tus propias pruebas unitarias y/o de integración.

He aquí un sencillo ejemplo de prueba unitaria en Java utilizando JUnit que comprueba en si el método addTwoIntegers() se comporta como se espera:

@Test
public int testAddTwoIntegers(int a, int b)
{
  // TODO: be sure to implement MyClass and the addTwoIntegers() method!
  MyClass mc = new MyClass();
  
  // baseline assertions
  assertTrue(mc.addTwoIntegers(0, 0) == 0);
  assertTrue(mc.addTwoIntegers(1, 2) == 3);
  assertTrue(mc.addTwoIntegers(-1, 1) == 0);
  assertTrue(mc.addTwoIntegers(-1, -2) == -3);
  assertFalse(mc.addTwoIntegers(1, 2) == 4);
  assertFalse(mc.addTwoIntegers(-1, -2) == -4);
}

¿Qué ocurre si tienes una única clase de prueba con dos pruebas unitarias individuales, pero sólo quieres ejecutar una? Simplemente añade @Ignore antes de la anotación @Test, y JUnit omitirá esa prueba en concreto. Elimina la anotación para volver a activar la prueba.

Veamos el mismo ejemplo de en Python, utilizando el framework unittest incorporado de Python 3:

def testAddTwoIntegers(self, a, b):
  // TODO: be sure to implement MyClass and the addTwoIntegers() method!
  MyClass mc = MyClass()
  
  # baseline assertions
  self.assertTrue(mc.addTwoIntegers(0, 0) == 0)
  self.assertTrue(mc.addTwoIntegers(1, 2) == 3)
  self.assertTrue(mc.addTwoIntegers(-1, 1) == 0)
  self.assertTrue(mc.addTwoIntegers(-1, -2) == -3)
  self.assertFalse(mc.addTwoIntegers(1, 2) == 4)
  self.assertFalse(mc.addTwoIntegers(-1, -2) == -4)

El framework unittest, al igual que JUnit, te permite desactivar pruebas específicas si lo deseas. Añade @unittest.skip("Put your reason here.") o @unittest.skip como anotación antes de la declaración del método, y el framework omitirá esa prueba específica.

Nota

Las pruebas unitarias de los repositorios python-components y java-components pueden ejecutarse como pruebas automatizadas desde la línea de comandos o desde el IDE. Es decir, puedes programarlas para que se ejecuten automáticamente como parte de tu construcción, y cada una de ellas pasará o fallará, dependiendo de la implementación de la unidad bajo prueba.

Las pruebas de integración son superimportantes para el IoT, ya que pueden utilizarse para verificar que las conexiones e interacciones entre sistemas y aplicaciones funcionan como se espera. Digamos que quieres probar un algoritmo de ordenación utilizando un conjunto de datos básico integrado en la clase de pruebas: normalmente escribirás una o varias pruebas unitarias, ejecutarás cada una de ellas y comprobarás que todo va bien.

Pero, ¿y si el algoritmo de ordenación necesita extraer datos de un repositorio de datos accesible a través de tu red local o incluso de Internet? ¿Y qué, te preguntarás? Bueno, ahora tienes otra dependencia sólo para ejecutar tu prueba de ordenación. Necesitarás una prueba de integración para verificar que la conexión al repositorio de datos está disponible y funciona correctamente antes de ejecutar la prueba unitaria de ordenación.

Este tipo de dependencias puede hacer que las pruebas de integración sean un reto en cualquier entorno, y más aún en el IoT, ya que a veces es necesario configurar servidores para ejecutar protocolos especializados para probar nuestras cosas. Por esta razón, y para mantener tu entorno de pruebas lo menos complicado posible, todas las pruebas de integración se ejecutarán y verificarán manualmente.

Nota

Ejecución y verificación manual significa que las pruebas de integración dentro de los repositorios python-components y java-components están diseñadas para que las ejecutes tú desde la línea de comandos y debas observarlas para determinar el éxito o el fracaso. Aunque algunas pueden ejecutarse técnicamente desde tu IDE e incluso pueden incluirse en un entorno automatizado de ejecución de pruebas, otras requieren cierta configuración antes de su ejecución (descrita en los propios comentarios de las pruebas o en la tarjeta de requisitos del módulo que se está probando). Te sugiero que te limites a ejecutarlas únicamente desde la línea de comandos.

Por último, las pruebas de rendimiento son útiles para comprobar la rapidez o eficacia con que un sistema gestiona diversas condiciones. Pueden utilizarse tanto con pruebas unitarias como de integración cuando, por ejemplo, haya que medir el tiempo de respuesta o el número de conexiones concurrentes o simultáneas admitidas.

Supongamos que hay muchos sistemas diferentes que necesitan recuperar una lista de datos de tu repositorio de datos, y cada uno de ellos quiere esa lista de datos ordenada antes de que tu aplicación se la devuelva. Ignorando por un momento el diseño del sistema y la optimización del esquema de la base de datos, se puede utilizar una serie de pruebas de rendimiento para cronometrar la capacidad de respuesta de la solicitud de cada sistema (desde la solicitud inicial hasta la respuesta), así como el número de sistemas concurrentes que pueden acceder a tu aplicación antes de que deje de responder adecuadamente.

Otro aspecto de las pruebas de rendimiento es comprobar la carga del sistema en el que se ejecuta tu aplicación, lo que puede ser muy útil para las aplicaciones IoT. Los dispositivos IoT suelen estar limitados de alguna manera -memoria, CPU, almacenamiento, etc.-, mientras que los servicios en la nube pueden escalar tanto como necesitemos. Es lógico, por tanto, que nuestras primeras aplicaciones IoT -que aparecerán en el Capítulo 2-preparen el escenario para monitorear el rendimiento de cada dispositivo individualmente.

Dado que las pruebas de rendimiento suelen ir de la mano de las pruebas de integración y unitarias, seguiremos utilizando Maven y pruebas unitarias especializadas también para esto, junto con herramientas de código abierto cuando sea necesario.

Nota

Las pruebas de rendimiento dentro de los repositorios python-components y java-components están todas diseñadas como pruebas manuales y deben ser observadas para determinar el éxito o el fracaso, de forma muy similar a las pruebas de integración descritas anteriormente. De nuevo, la automatización es técnicamente factible, pero queda fuera del alcance de este libro. Asegúrate de revisar los procedimientos de configuración de cada prueba antes de su ejecución, que se describen como parte del ejercicio o están contenidos en la tarjeta de requisitos del módulo.

Existen muchas herramientas de comprobación del rendimiento, y también puedes escribir las tuyas propias. Las pruebas de rendimiento de sistema a sistema y de protocolo de comunicaciones son completamente opcionales a efectos de este libro, y sólo tocaré brevemente este tema en el Capítulo 10. Si quieres saber más sobre las pruebas de rendimiento personalizadas, puedes buscar herramientas diseñadas para este fin, como Locust, que te permite escribir tus propias pruebas de rendimiento e incluye una interfaz de usuario (UI) basada en web.

Consejos para probar los ejercicios de este libro

El código de ejemplo proporcionado para cada ejercicio de este libro incluye pruebas unitarias, que puedes utilizar para probar el código que vas a escribir. Estas pruebas unitarias, que se proporcionan como parte de los repositorios java-components y python-components que ya has incorporado a tus proyectos GDA y CDA (respectivamente), son fundamentales para garantizar que tu implementación funciona correctamente.

Algunos ejercicios también tienen pruebas de integración que puedes utilizar tal cual o modificar para adaptarlas a tus necesidades específicas. También he incluido algunos ejemplos de pruebas de rendimiento que puedes utilizar para comprobar el rendimiento de parte de tu código bajo carga.

Tu implementación de cada ejercicio debe superar cada una de las pruebas unitarias proporcionadas con un 100% de éxito. Puedes añadir más pruebas unitarias si crees que serán útiles para verificar la funcionalidad que desarrolles. Las pruebas de integración y de rendimiento proporcionadas también serán herramientas de validación útiles a medida que implementes cada ejercicio.

Recuerda que los tests son tus amigos y, como a un amigo, no hay que ignorarlos. Sin duda, escribirlas y mantenerlas puede llevar mucho tiempo, pero toda buena amistad requiere inversión. Estas pruebas -ya sean unitarias, de integración o de rendimiento- te ayudarán a validar tu diseño y a verificar que tu funcionalidad funciona correctamente.

Paso III: Gestiona tu flujo de trabajo de diseño y desarrollo

Así que has averiguado cómo quieres escribir tu código y probarlo: ¡genial! Pero, ¿no sería estupendo que pudieras gestionar todos tus requisitos, código fuente y canalizaciones CI/CD? Vamos a abordar esto en nuestro último paso, que consiste en gestionar el flujo de trabajo general de tu proceso de desarrollo. Esto incluye el seguimiento de requisitos, la gestión del código fuente y la automatización CI/CD.

Probablemente estés harto de que diga que construir sistemas IoT es difícil, y eso se debe en gran parte a la naturaleza del perímetro (ya que a menudo tenemos que lidiar con diferentes tipos de dispositivos, paradigmas de comunicación, entornos operativos, restricciones de seguridad, etc.). Afortunadamente, hay muchas herramientas modernas de CI/CD que pueden utilizarse para ayudar a navegar por estas aguas turbulentas. Veamos algunos requisitos de selección para estas herramientas, y luego exploremos cómo construir una canalización CI/CD que funcione para nuestras necesidades.

Tu canal de CI/CD de IoT debe admitir autenticación y autorización seguras, capacidad de scripting desde una línea de comandos similar a la de Linux, integración con Git e infraestructura de contenedorización, y capacidad para ejecutar canalizaciones en tu entorno local, así como en un entorno alojado en la nube.

Hay muchos servicios en línea que proporcionan estas funciones, algunos de los cuales también ofrecen niveles de servicio gratuitos y de pago. Cuando descargaste el código fuente de este libro, lo sacaste de mis repositorios de GitHub utilizando la función de clonación de Git. GitHub es un servicio en línea que facilita la gestión general del flujo de trabajo de los desarrolladores, incluido el control del código fuente (mediante Git), la automatización CI/CD y la planificación.

En cada ejercicio se construirá, probará e implementará localmente, pero también se asumirá que tu código está comprometido en un repositorio en línea utilizando Git para la gestión del código fuente. Por supuesto, puedes utilizar el servicio en línea que prefieras. Para este libro, todos los ejemplos y ejercicios supondrán que se utiliza GitHub.7

Nota

Hay un montón de recursos, herramientas y servicios en línea fantásticos que te permiten gestionar tu trabajo de desarrollo y establecer canalizaciones automatizadas de CI/CD. Lee esta sección, prueba cosas y, cuando adquieras más experiencia, elige las herramientas y el servicio que mejor se adapten a ti.

Gestionar las necesidades

Ah, sí: requisitos. ¿Qué vamos a construir, a quién le importa, y cómo vamos a construirlo? Los planes son buenos, ¿no? Y puesto que son buenos, deberíamos tener una herramienta que abarque la bondad, con funciones como la priorización de tareas, el seguimiento de tareas, la colaboración en equipo y (tal vez) la integración con otras herramientas.

Los repositorios CDA y GDA incluyen implementaciones de shell (y algunas implementaciones completas) de las clases e interfaces que completarás siguiendo los requisitos del ejercicio de codificación de cada capítulo. Todos los requisitos -incluidas algunas notas informativas- pueden encontrarse en otro repositorio de GitHub que he puesto a tu disposición en el proyecto Programando el IoT. Este es el mejor lugar para empezar, ya que proporciona una lista ordenada de actividades en columnas y filas, como es típico en un tablero Kanban.

Nota

De vez en cuando, actualizaré los ejercicios del tablero Kan ban y las tarjetas de instrucciones, junto con ajustes en los repositorios de código fuente Python y Java. De este modo, el tablero Kanban tendrá la información más actualizada sobre cualquier ejercicio tratado en el libro. Si encuentras alguna discrepancia entre el libro y el contenido online de GitHub, remítete a este último, que debería ser el más preciso.

Los requisitos específicos de cada columna se capturan como tarjetas y en realidad hacen referencia a "Cuestiones" del repositoriobook-exercise-tasks que he creado para facilitar la gestión centralizada de los requisitos. Puedes profundizar fácilmente en cualquiera de estos requisitos haciendo clic en su nombre o abriéndolo en una pestaña aparte.

La convención de nomenclatura de cada ficha debería ser relativamente fácil de entender: {book}-{app type}-{chapter}-{number}. Por ejemplo, PIOT-CDA-01-001, que se refiere a Programación de la Internet de las Cosas (PIOT), Aplicación para Dispositivos Restringidos (CDA), Capítulo 1 (01), requisito nº 1 (001).

Ese último número es importante, ya que indica la secuencia que debes seguir. Por ejemplo, el requisito nº 2 (002) seguiría al requisito nº 1 (001), y así sucesivamente. El contenido de cada requisito contiene las instrucciones de implementación que tú, el programador, debes seguir, seguidas de las pruebas que debes ejecutar para verificar que el código funciona correctamente.

Hay dos números especiales que debes tener en cuenta, aunque también siguen la secuencia. Todas las tareas que terminan en "000" son tareas relacionadas con la configuración, como la creación de tu rama. Todas las tareas que terminan en "100" son tareas relacionadas con la fusión y la validación, como fusionar tu rama capítulo en la principal y verificar que todas las funciones funcionan como se espera.

Todas estas tarjetas y notas se organizan en el tablero Kanban del libro, con una sola columna para cada capítulo, de modo que puedas ver todo lo que hay que poner en práctica en los ejercicios de cada capítulo. Probablemente hayas oído hablar de los procesos de gestión de proyectos ágiles8 como Scrum y Kanban. Con un tablero Kanban, la idea es seleccionar una tarjeta, empezar a trabajar en ella y, una vez probada, verificada, revisada y comprometida, cerrarla.

Aunque no puedas tirar de las tarjetas que te proporciono y cerrar ninguno de estos temas, están disponibles para que los revises y hagas un seguimiento por tu cuenta a medida que avanzas en los ejercicios de cada capítulo. A partir del capítulo 2, cuando empieces a escribir código, hablaré de cómo gestionar tus requisitos CDA y GDA dentro de tus propios repositorios; de momento, te daré una rápida visión general de cómo he configurado las tarjetas de requisitos (que no son más que referencias a una o varias incidencias de repositorios) dentro del tablero Kanban de Programando el IoT.

Nota

Yo también estoy gestionando las actividades de este libro dentro de un tablero Kanban. Cada tarjeta del tablero representa una tarea que yo o uno de los miembros de mi equipo debe completar. Una tarjeta pasa a "Hecho" sólo cuando el equipo acuerda que está completa.

GitHub proporciona una pestaña "Cuestiones" para hacer un seguimiento de los requisitos y otras notas relacionadas con tu repositorio. La Figura 1-12 muestra la plantilla de tarea que utilicé para cada requisito a lo largo del libro. Esto es lo que va en cada tarea y puede contener texto, enlaces, etc. Observa que cada una de las fichas de requisitos que he creado contiene cinco elementos: Título, Descripción, Acciones, Estimación y Pruebas.

Task template
Figura 1-12. Plantilla de tareas

La mayoría de estas categorías se explican por sí mismas. Pero, ¿por qué sólo tres niveles de esfuerzo para Estimar? En este libro, la mayoría de las actividades deberían entrar en una de las siguientes categorías de "nivel de esfuerzo": 2 horas o menos (Pequeño), alrededor de medio día (Medio) o alrededor de un día (Grande). Ten en cuenta que son sólo aproximaciones y que variarán mucho en función de diversos factores.

Por ejemplo, una "tarea" con el nombre Integrar la solución IoT con tres servicios en la nube representa sin duda trabajo que puede ser necesario realizar, pero a juzgar sólo por el nombre, es claramente demasiado grande y complicada para ser una única actividad de trabajo. En este caso, puedo crear varias Ediciones, con un conjunto de pruebas específicas para cada una de ellas. En otros casos, puedo tener varios módulos muy básicos con implementaciones similares, todos ellos contenidos en la misma incidencia. Intento que cada cuestión sea lo más autónoma posible.

La Figura 1-13 muestra un ejemplo de la plantilla rellenada con lo más destacado de la primera tarea de codificación que tendrás: crear la Aplicación de Dispositivo Restringido (CDA).

Example of a typical development task
Figura 1-13. Ejemplo de una tarea de desarrollo típica

Y la Figura 1-14 muestra el resultado de añadir la tarea como tarjeta Kanban. Esta tarjeta se generó automáticamente tras alinear la tarea con un proyecto. Observa que se ha añadido a la columna "Por hacer" del tablero, ya que es nueva y aún no tiene estado. En cuanto empieces a trabajar en la tarea y cambies su estado, pasará a "En curso".

De nuevo, los requisitos ya están escritos para ti y están contenidos en el tablero Kanban de Programar el IoT, pero ahora deberías tener una mejor idea de cómo se definen estos requisitos, e incluso una plantilla para crear los tuyos propios, si decides hacerlo.

The new example task added into the Kanban board
Figura 1-14. La nueva tarea de ejemplo añadida a la tabla Kanban

A continuación, vamos a configurar tus repositorios Git remotos.

Configurar tus repositorios remotos

Existen muchos servicios excelentes de repositorios Git basados en la nube. Como he mencionado, yo utilizo GitHub, por lo que he proporcionado aquí algunas instrucciones opcionales para ayudarte a empezar:

  1. Crea una cuenta de GitHub. (Si lo deseas, crea una organización asociada a la cuenta de GitHub).

  2. Dentro de tu cuenta (u organización), crea lo siguiente:

    1. Un nuevo proyecto privado llamado "Programando el IoT - Ejercicios"

    2. Un nuevo repositorio Git privado llamado "java-components"

    3. Un nuevo repositorio Git privado llamado "python-components"

  3. Actualiza el repositorio remoto tanto para "java-components" como para "python-components".

    1. Desde la línea de comandos, ejecuta los siguientes comandos:

      git remote set-url origin {your new URL}
      git commit -m “Initial commit.”
      git push
    2. IMPORTANTE: ¡Asegúrate de hacer esto tanto para "java-components" como para "python-components", utilizando la URL del repositorio Git adecuada para cada uno!

Una vez que completes estas tareas, tus repositorios Git estarán en su sitio, y podrás gestionar tu código localmente y sincronizarlo con tu instancia remota.

Gestión del código y ramificación

Una de las principales ventajas de utilizar Git es la posibilidad de colaborar con otros y sincronizar tu repositorio de código local con un repositorio remoto almacenado en la nube. Si has trabajado antes con Git, ya estarás familiarizado con los remotos y la ramificación. No voy a entrar en muchos detalles aquí, pero son conceptos importantes que debes comprender como parte de tu entorno de automatización.

La ramificación es una forma de permitir a cada desarrollador o equipo segmentar su trabajo sin afectar negativamente a la base de código principal. En Git, esta rama principal por defecto se denomina actualmente "master" y suele utilizarse para contener el código que se ha completado, probado, verificado y (normalmente) puesto en producción. Esta es la rama por defecto tanto para "java-components" como para "python-components", y aunque puedes dejarla como está y simplemente trabajar a partir de esta rama por defecto, generalmente no se recomienda por las razones que he mencionado.

Las estrategias de ramificación pueden variar de una empresa a otra y de un equipo a otro; la que a mí me gusta utilizar tiene cada capítulo en una nueva rama, y luego, una vez que todo funciona correctamente y se ha probado adecuadamente, la rama del capítulo se fusiona con la maestra. A partir de ahí, se crea una nueva rama desde la maestra fusionada para el siguiente capítulo, y así sucesivamente.

Este enfoque te permite rastrear fácilmente los cambios entre capítulos, e incluso volver al registro histórico de un capítulo anterior si quieres ver qué cambió entre, por ejemplo, el Capítulo 2 y el Capítulo 5. En Eclipse, puedes hacer clic con el botón derecho en el proyecto (ya sea "java-components" o "python-components") y elegir Equipo → "Cambiar a" → "Nueva rama" para establecer una nueva rama para tu código.

Te sugiero que utilices la convención de nomenclatura "capternn" para el nombre de cada rama, donde nn es el número de dos dígitos del capítulo. Por ejemplo, la rama del capítulo 1 se llamará "capítulo01", la del capítulo 2 se llamará "capítulo02", y así sucesivamente. Es útil disponer de una estrategia de bifurcación que te permita volver a una bifurcación anterior, la "última buena conocida", o al menos ver qué ha cambiado entre un capítulo y el siguiente. He documentado la estrategia de bifurcación basada en capítulos dentro de los requisitos de cada capítulo a modo de recordatorio.

Nota

Los detalles escabrosos sobre la bifurcación y la fusión en Git quedan fuera del alcance de este libro, así que te recomiendo que leas la guía "Git Branching-Basic Branching and Merging" si quieres profundizar en ellos.

Reflexiones sobre la automatización

Aunque queda fuera del alcance de este libro, la automatización de las compilaciones, pruebas, integración e implementación de software es una parte clave de muchos entornos de desarrollo. Discutiré algunos conceptos en esta sección, pero no la abordaré en esta versión actual del libro.

CI/CD automatizado en la nube

Dentro de Eclipse, puedes escribir tu código CDA y GDA, ejecutar pruebas unitarias y construir y empaquetar ambas aplicaciones. En realidad, esto no está automatizado, ya que tienes que iniciar tú mismo el proceso ejecutando un comando como mvn install desde la línea de comandos o invocando el proceso de instalación de Maven desde dentro del IDE. Esto es estupendo para llevar ambas aplicaciones a un punto en el que puedas ejecutarlas, pero no las ejecuta realmente: aún tienes que iniciar manualmente las aplicaciones y luego ejecutar tus pruebas de integración y/o rendimiento.

Como desarrollador, parte de tu trabajo consiste en escribir y probar código para cumplir los requisitos que se han capturado (en tarjetas en un tablero Kanban, por ejemplo), por lo que siempre hay algo de trabajo manual. Una vez que sepas que tus unidades de código funcionan correctamente, hacer que todo lo demás se ejecute automáticamente -digamos, después de confirmar y enviar tu código a la rama de desarrollo remota (como "chapter02", por ejemplo)- sería bastante hábil.

GitHub admite esta automatización a través de las acciones de GitHub.9 Hablaré más sobre esto en el Capítulo 2 y te ayudaré a configurar tu propia automatización para las aplicaciones que vayas a construir.

CI/CD automatizado en tu entorno de desarrollo local

Hay muchas formas de gestionar el CI/CD dentro de tu entorno local. Por ejemplo, las acciones de GitHub pueden ejecutarse localmente mediante ejecutores autoalojados.10 En también hay una herramienta de automatización del flujo de trabajo llamada Jenkins que puede ejecutarse localmente, se integra perfectamente con los repositorios locales y remotos de Git, y tiene una arquitectura de plug-ins que te permite ampliar sus capacidades aparentemente hasta el infinito.

Advertencia

Hay muchos complementos de Jenkins de terceros y otras utilidades que me han resultado útiles para mi propio entorno de creación, prueba e implementación, pero debes investigar por tu cuenta para determinar cuáles se mantienen activamente y aportarán valor a tu entorno específico. Es fácil introducir problemas de compatibilidad del sistema e incluso vulnerabilidades de seguridad si no eres plenamente consciente de lo que un producto hará o dejará de hacer. En última instancia, es tu responsabilidad tomar esta decisión.

Una vez instalado y protegido, puedes configurar Jenkins para que monitorice automáticamente tu repositorio Git local o remotamente y ejecute un flujo de trabajo de compilación/test/despliegue/ejecución en tu sistema local, comprobando el éxito en cada paso. Si, por ejemplo, la compilación falla debido a un error de compilación en tu código, Jenkins informará de ello y detendrá el proceso. Lo mismo ocurre si la compilación tiene éxito pero las pruebas fallan: el proceso se detiene en el primer punto de fallo. Esto garantiza que tu implementación local no se sobrescriba con una actualización que no compile o que no ejecute correctamente las pruebas configuradas.

Configurar cualquier herramienta de automatización local puede ser una tarea complicada y lenta. Sin embargo, es muy útil, ya que básicamente automatiza todo lo que vas a hacer para crear, probar e implementar tu software. Dicho esto, no es necesario para ninguno de los ejercicios de este libro, por lo que no entraré en ello aquí.

Contenedorización

Es probable que hayas oído hablar de la contenedorización, que es una forma de empaquetar tu aplicación y todas sus dependencias en una sola imagen, o contenedor, que puede desplegarse en muchos entornos operativos diferentes. Este enfoque es muy conveniente, ya que te permite crear tu software y desplegarlo de forma que el entorno de alojamiento deje de ser una preocupación, siempre que el entorno de destino admita la infraestructura de contenedores que estés utilizando.

Docker11 es esencialmente un motor de aplicaciones que se ejecuta en diversos sistemas operativos, como Windows, macOS y Linux, y sirve de anfitrión para tu(s) instancia(s) de contenedor. Tu GDA y tu CDA, por ejemplo, pueden contenerizarse y luego implementarse en cualquier dispositivo de hardware que admita la infraestructura de contenedores y el tiempo de ejecución subyacentes.

Merece la pena señalar que contenerizar cualquier aplicación que tenga código específico de hardware puede ser problemático, ya que no será portátil a otra plataforma de hardware diferente (aunque el motor del contenedor sea compatible). Si quieres que tu aplicación específica de hardware funcione en cualquier plataforma que admita Docker, esa plataforma requeriría un emulador específico de hardware compatible con el código desarrollado para la aplicación.

Por ejemplo, si tu CDA tiene código que depende de hardware específico de Raspberry Pi, eso nos preocupa menos por el momento, ya que emularás sensores y actuadores y no tendrás que preocuparte de ningún código específico de hardware hasta el Capítulo 4 (que, de nuevo, es opcional). Hablaré más de esto en el Capítulo 4, junto con estrategias para superar la especificidad del hardware en tu CDA.

Cuando utilices canalizaciones CI/CD en un entorno remoto o en la nube, observarás que estos servicios probablemente se desplegarán en máquinas virtuales y ejecutarán tu código dentro de un contenedor que incluye las dependencias necesarias, todo ello configurado como parte de la canalización. En muchos casos, esto tiene mucho sentido y puede ser una estrategia eficaz para garantizar la coherencia y la facilidad de implementación. La salvedad es que la plataforma de destino debe ser compatible con el entorno de ejecución del contenedor que quieras implementar. Los lenguajes multiplataforma pueden facilitar esto, pero es un punto doloroso que no espero que desaparezca pronto.

Para simplificar las cosas, no voy a explicarte cómo utilizar la contenedorización en tu entorno de desarrollo y como parte de tu estación de trabajo, aunque hacerlo tenga muchas ventajas. La razón principal es que añade otra capa de complejidad que gestionar inicialmente, y quiero que te pongas en marcha con tus propias aplicaciones lo antes posible.

Ejercicios de programación

Todo el trabajo que has hecho hasta ahora es para prepararte para construir tu CDA y GDA. Tienes algunos conocimientos iniciales sobre el IoT y un entorno de desarrollo configurado y estás listo para codificar. Hasta aquí todo bien, ¿verdad?

Si echas un vistazo a tu base de código, verás que ya tienes un montón de componentes. De hecho, la mayoría no son más que implementaciones en shell de los componentes necesarios tanto para el CDA como para el GDA. Pero, ¿cómo se supone que van a funcionar todos juntos?

Los capítulos restantes del libro te guiarán a través de los requisitos documentados en el tablero Kanban de Programación del IoT para alcanzar este estado final. Las Figuras 1-15 y 1-16 muestran el enfoque de diseño general que seguiremos para llegar a ese punto para el CDA y el GDA, respectivamente.

CDA end-state design
Figura 1-15. Diseño del estado final del CDA
GDA end-state design
Figura 1-16. Diseño del estado final del GDA

¡Parece mucho trabajo! Pero no te preocupes: iremos paso a paso. Cada capítulo añade más funcionalidad a cada uno de estos diagramas utilizando la codificación por colores de la leyenda (para componentes existentes, nuevos y modificados), junto con la profundización en cada área que aborda el capítulo.

Echemos un vistazo a los diseños específicos del CDA y el GDA que son relevantes para este capítulo y repasemos los ejercicios (sólo hay uno para cada aplicación). Puedes consultar los detalles de cada uno en línea: PIOT-CDA-01-001 para el CDA y PIOT-GDA-01-001 para el GDA.

CDA design for Chapter 1: GDA design for Chapter 1
Figura 1-17. Diseño CDA del Capítulo 1 (izquierda) y diseño GDA del Capítulo 1 (derecha)

¡Estos parecen un poco más manejables! De hecho, ¡ya has completado el primero -y quizás el más importante- de todos ellos y has aplicado todos los requisitos del Capítulo 1! Sólo te queda probarlos. En la sección Prueba de cada tarjeta de requisitos, verás que tienes que ejecutar cada aplicación como una prueba de integración manual.

Probar la aplicación para dispositivos limitados

Para el CDA, navega hasta la ruta ./src/main/python/programmingtheiot/part01/integration/app. Verás la prueba ConstrainedDeviceAppTest. Después de ejecutar esta prueba, tu salida debería ser similar a la siguiente:

Finding files... done.
.
2020-12-30 13:54:32,915 - MainThread - root - INFO - Testing ConstrainedDeviceApp
class...
2020-12-30 13:54:32,915 - MainThread - root - INFO - Initializing CDA...
2020-12-30 13:54:32,915 - MainThread - root - INFO - Starting CDA...
2020-12-30 13:54:32,916 - MainThread - root - INFO - Loading config:
../../../../../../../config/PiotConfig.props
2020-12-30 13:54:32,917 - MainThread - root - DEBUG - Config:
['Mqtt.GatewayService', 'Coap.GatewayService', 'ConstrainedDevice']
2020-12-30 13:54:32,917 - MainThread - root - INFO - Created instance of
ConfigUtil: <programmingtheiot.common.ConfigUtil.ConfigUtil object at
0x0000026B463E0E48>
2020-12-30 13:54:32,917 - MainThread - root - INFO - CDA started.
2020-12-30 13:54:32,917 - MainThread - root - INFO - CDA stopping...
2020-12-30 13:54:32,917 - MainThread - root - INFO - CDA stopped with exit code 0.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

Puede que observes numerosas declaraciones de salida del registro de carga del módulo (que he excluido); no pasa nada. Si tu salida sigue en general el patrón que se muestra aquí, puedes pasar a probar el GDA.

Probar la aplicación del dispositivo Gateway

Para probar la GDA, navega hasta la ruta ./src/main/java/programmingtheiot/part01/integration/app. Verás la prueba GatewayDeviceAppTest. Tu salida será parecida a la siguiente:

Dec 30, 2020 1:45:50 PM programmingtheiot.gda.app.GatewayDeviceApp <init>
INFO: Initializing GDA...
Dec 30, 2020 1:45:50 PM programmingtheiot.gda.app.GatewayDeviceApp parseArgs
INFO: No command line args to parse.
Dec 30, 2020 1:45:50 PM programmingtheiot.gda.app.GatewayDeviceApp startApp
INFO: Starting GDA...
Dec 30, 2020 1:45:50 PM programmingtheiot.gda.app.GatewayDeviceApp startApp
INFO: GDA started successfully.
Dec 30, 2020 1:45:51 PM programmingtheiot.gda.app.GatewayDeviceApp stopApp
INFO: Stopping GDA...
Dec 30, 2020 1:45:51 PM programmingtheiot.gda.app.GatewayDeviceApp stopApp
INFO: GDA stopped successfully with exit code 0.

Si el resultado es similar, entonces estás preparado para empezar a escribir código.

Conclusión

Enhorabuena, acabas de terminar el primer capítulo de Programar el Internet de las Cosas. Has aprendido algunos principios básicos de IoT, has creado un planteamiento del problema para impulsar tu solución de IoT y has establecido una arquitectura básica de sistemas de IoT que incluye el nivel de nube y el nivel de perímetro.

Quizás lo más importante es que ya tienes el armazón de dos aplicaciones -el GDA y el CDA- que servirán de base para gran parte de tu desarrollo de software IoT a lo largo de este libro. Por último, has configurado tu entorno de desarrollo y tu flujo de trabajo; has aprendido sobre gestión de requisitos; has explorado las pruebas unitarias, de integración y de rendimiento; y has considerado algunos conceptos básicos de CI/CD para ayudarte a automatizar tus compilaciones e implementaciones.

Ya estás preparado para empezar a construir una funcionalidad IoT real en tu CDA y GDA utilizando Python y Java. Si estás listo para seguir adelante, te sugiero que te tomes un poco de agua o una buena taza de café o té, y luego entraremos en materia.

1 Carsten Bormann, Mehmet Ersue y Ari Keränen, "Terminology for Constrained-Node Networks", IETF Informational RFC 7228, mayo de 2014, 8-10.

2 Obtén más información sobre WSL y cómo instalarlo en tu plataforma en https://oreil.ly/YK9Rb.

3 CoAP, o Protocolo de Aplicación Restringida, es un protocolo de mensajería del que hablaré en la Parte III (concretamente, en los Capítulos 8 y 9).

4 MQTT, o Message Queuing Telemetry Transport, es un protocolo de mensajería del que hablaré en la Parte III (concretamente, en los Capítulos 6 y 7).

5 Puedes encontrar más información en el sitio web de GitHub.

6 Puedes encontrar información detallada sobre la biblioteca unittest de Python 3 en la documentación de unittest.

7 Puedes obtener más información sobre GitHub y sus funciones de alojamiento y crear una cuenta gratuita en el sitio web de GitHub.

8 Puedes obtener más información leyendo el Manifiesto Ágil.

9 Las acciones de GitHub son una función disponible en GitHub que permite crear flujos de trabajo personalizados para quienes tengan una cuenta en GitHub.

10 Los ejecutores autoalojados, que forman parte de las acciones de GitHub, te permiten ejecutar localmente tus flujos de trabajo de acciones. Hay advertencias, por supuesto, y consideraciones de seguridad. Puedes leer más sobre los ejecutores autoalojados en la documentación de las acciones de GitHub.

11 Puedes leer más sobre los conceptos de contenedorización y los productos de contenedorización de Docker en el sitio web de Docker.

Get Programar el Internet de las Cosas now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.