Erlang para IoT

La ola de interés en los dispositivos microelectrónicos y su interacción entre ellos para las necesidades industriales y domésticas ha llevado al desarrollo de una gran cantidad de diseñadores para el desarrollo sobre la base de SoC (sistemas en un chip) lo suficientemente potentes, en miniatura con respecto a las soluciones de microcontroladores, pero que ya contienen un sistema operativo completo. El desarrollo de aplicaciones para tales diseñadores prácticamente no difiere del desarrollo habitual del servidor, excepto que aún se debe tener en cuenta el límite de recursos.



A medida que la productividad y las capacidades crecen, la práctica de usar lenguajes interpretados de alto nivel como Lua, Python, JS para el desarrollo de aplicaciones está ganando más impulso. Sin embargo, algunos idiomas penetran gradualmente en los "hermanos menores", microcontroladores, en una forma muy limitada.

Hay varias razones para esto:

  • creación rápida de prototipos: con el debido respeto al lenguaje C, que desarrolla principalmente microcontroladores, es muy difícil llamarlo conciso. Los lenguajes de alto nivel le permiten escribir menos código y simplificar la realización de cambios a lo que ya está escrito, lo cual es muy importante en la etapa de prototipo;
  • gestión automática de memoria y abstracción de mecanismos de computación complejos: creo que no necesita comentarios, ambos procesos manuales con un volumen de proyecto suficiente se convierten en una fuente de mucho dolor de cabeza;
  • simplificación de depuración y prueba: el código interpretado es más fácil de verificar en la estación de trabajo antes del momento de la prueba de campo;
  • lucha con la complejidad: a menudo, la gran productividad da lugar a un deseo pragmático natural de impulsar más preprocesamiento y análisis en el dispositivo, lo que no agrega simplicidad al desarrollo.

Por desgracia, tienes que pagar por todas las comodidades. En este caso, el precio por conveniencia son los recursos, el rendimiento y el tamaño del código más valiosos (en muchos casos, debe llevar un entorno de tiempo de ejecución bastante voluminoso). Por lo tanto, la aplicación de campo de lenguajes de alto nivel en SoC y SoM es algo ambiguo y, en algunos lugares, un compromiso.

Utilizamos el lenguaje Erlang para el desarrollo, aplicándolo tanto para su propósito previsto (crear aplicaciones de servidor y un plano de control) como muy poco comunes: aplicaciones web. Por lo tanto, la idea de utilizar la infraestructura de este lenguaje para crear soluciones de IoT surgió mucho antes de la aparición de placas en las que el tiempo de ejecución de Erlang podría funcionar sin problemas.

Las razones para usar Erlang fueron muchas, las más significativas:

  • Erlang es muy conveniente para analizar y crear secuencias binarias. La coincidencia de patrones combinada con el procesamiento de datos de bits permite la implementación de protocolos binarios de manera muy rápida y sin palabras;
  • falta de variables globales e inmutabilidad para la mayoría de los datos: le permite escribir y, no menos importante, mantener aplicaciones confiables en las que es difícil cambiar accidentalmente algo incorrecto;
  • Procesos de mensajería livianos que son muy similares a los que tienen que enfrentar los desarrolladores integrados. En esencia, son un procedimiento de inicialización y un procedimiento que procesa los mensajes entrantes en un bucle infinito, cambiando el estado interno. Es muy similar a Arduino, solo puede haber muchos procesos y funcionan en paralelo, además, en el intervalo entre mensajes, el procedimiento de procesamiento se puede cambiar sobre la marcha (recarga de código activo), lo cual es muy conveniente cuando necesita corregir errores menores o ajustar el comportamiento;
  • entorno aislado y asignación automática de memoria, creo, no necesitan explicación;
  • multiplataforma: el código de bytes para el tiempo de ejecución ERTS puede compilarse en la máquina del desarrollador y luego transferirse al dispositivo de destino sin problemas;
  • Excelentes herramientas de introspección: la capacidad de conectarse a una aplicación en ejecución a través de la red y ver que se ralentiza tan a menudo suele ser muy útil.

La primera mesa de trabajo en la que probamos Erlang fue Carambola 2 de los desarrolladores lituanos de 8 dispositivos , ensamblados en el popular chip AR9331. La primera versión de esta placa, por desgracia, no tenía suficiente memoria flash para el tiempo de ejecución. Pero la segunda versión ya permitía acomodar ERTS y una pequeña aplicación.



La instalación se llevó a cabo mediante un método clásico para este tipo de dispositivo: ensamblar una imagen OpenWRT que contiene Erlang, seguido de flashearla en la memoria flash del dispositivo. El primer lanzamiento del medio ambiente, por desgracia, provocó desilusión: todo quedó colgado. Ya expliqué las razones de esto en la conferencia InoThings 2018 , pero, por desgracia, como resultó más tarde, engañé a mis colegas al nombrar por error la fuente de tal comportamiento.

Lo volveré a contar brevemente. Al trabajar con archivos, la máquina virtual ERTS usa el tipo off_t , cuyo tamaño en la distribución se calcula cuando el ensamblaje se configura automáticamente (si se ejecuta en la plataforma de destino) o se sustituye desde el entorno de compilación cruzada, como sucedió en el caso de OpenWRT. No está claro por qué, pero en la configuración de los procesadores MIPS y derivados en el archivo de configuración de ensamblaje es dos veces más grande que en realidad. El problema no surgiría si el código de la máquina virtual no utilizara directivas de preprocesador como

#if SIZEOF_OFF_T == 4 

y una verificación banal en el código (sospecho que el resultado final de la compilación sería el mismo, pero no había suficiente fusible para verificar):

 if (sizeof(off_t) == 4) { 

Como resultado, el ERTS se recopiló en la primera iteración al intentar leer el archivo ~ / .erlang.cookie (un tipo de contraseña para la identificación durante la interacción de la red) al inicio recibió basura con éxito en el orden superior y se estrelló con una explosión.

El parche y el paquete con la penúltima versión de ERTS para OpenWRT se pueden descargar desde GitHub . Además de esto, todavía no se han observado problemas, todo funcionó como se esperaba.

La segunda plataforma de hardware en la que probamos Erlang fue el diseñador LinkIt Smart 7688 de Mediatek y SeeedStudio , especialmente diseñado para la creación rápida de prototipos y el aprendizaje de los conceptos básicos. Esta placa es solo la apoteosis del libertinaje en términos de recursos: la frecuencia del núcleo MIPS ha crecido 1.5 veces, más RAM (para ERTS es importante, GC no duerme) y más memoria flash, así como la presencia de una tarjeta microSD y la posibilidad de usar el coprocesador Atmel Atmega 32U4 en la versión Duo para trabajar con periféricos.

En general, la plataforma era muy adecuada para continuar el banquete, y la presencia de dispositivos adicionales que se conectan sin soldar le permite ensamblar rápida y condicionalmente un banco de pruebas en la rodilla.

La plataforma viene con software con su propia interfaz web, así como bibliotecas Python y NodeJS para el desarrollo. El ecosistema para el ensamblaje no ha cambiado, sigue siendo OpenWRT. Si por alguna razón encuentra toda esta diversidad superflua, entonces en el repositorio anterior hay paquetes que contienen un conjunto mínimo de componentes requeridos . Después del ensamblaje, la imagen flash se escribe en el dispositivo y después de reiniciar, puede usar REPL de forma segura.

Para crear aplicaciones en Erlang para IoT, se debe resolver un problema arquitectónico.

El lenguaje fue diseñado y desarrollado teniendo en cuenta lo que servirá como plano de control, es decir, capa de control, mientras se trabajaba con hierro se suponía que era a través de FFI. Se proporcionan tres tipos de interacción para esto:

  1. puertos (puertos): procesos de trabajo separados escritos en cualquier idioma, cuya interacción ocurre a través de flujos de entrada / salida. Se pueden reiniciar en caso de una caída, pero debido al método de interacción, su productividad en términos de comunicación es pequeña (sin embargo, será suficiente para nosotros);
  2. Funciones NIF: parecen funciones estándar del lenguaje, pero su llamada genera la ejecución del código compilado en el espacio de tiempo de ejecución. En caso de error, pueden arrastrar toda la máquina virtual detrás de ellos.
  3. Nodo C: cuando todo el trabajo se realiza en un proceso separado y la interacción se lleva a cabo como con un entorno de tiempo de ejecución que se ejecuta por separado en la red. No consideraremos esta opción debido a los costos generales suficientemente altos dentro del marco de un dispositivo débil.

El dilema es este: solo podemos transportar cosas a FFI (es decir, soporte para GPIO, I2C, SPI, PWM, UART, etc.), y podemos interactuar directamente con sensores y otros dispositivos en Erlang, o por el contrario, para transferir controladores de dispositivos completamente al código de módulos externos, dejando que la aplicación reciba datos sin procesar y los procese, en este caso, puede tener sentido usar el código ya escrito.

Decidimos usar la primera opción. Hay varias razones para esto:

  • porque podemos
  • Como ya mencioné, Erlang viene con herramientas lo suficientemente poderosas para ensamblar y desarmar secuencias binarias, mientras que es una matemática bastante simple, que le permite no preocuparse por la protección contra desbordamientos y otra magia utilizada para procesar los resultados. No es que esta magia espantapájaros, pero afecta a los neófitos de manera impactante;
  • intuitivamente, parecía que los controladores en un lenguaje de alto nivel serían más simples y confiables (un proceso bloqueado reiniciará el supervisor, lo que conducirá a la reinicialización del dispositivo controlado).

Por lo tanto, se encontró rápidamente la biblioteca ErlangALE , que contenía soporte ya implementado para GPIO, I2C y SPI a través de las interfaces del núcleo, y los desarrolladores de la plataforma de hardware, a su vez, ya se habían ocupado de ellos. La biblioteca para trabajar con UART ya fue probada, además agregamos erlexec , una aplicación que le permite crear y administrar procesos del sistema operativo.

Todas estas aplicaciones usaban puertos (procesos binarios iniciados por separado) para trabajar con el equipo y el sistema operativo, lo que requería soporte de compilación cruzada para los lenguajes C y C ++, para lo cual se escribió un script de shell bastante elaborado que configuraba el entorno de compilación para usar los compiladores necesarios.

Para probar las decisiones que tomamos, ensamblamos un dispositivo simple de LinkIt Smart 7866, dos dispositivos I2C (sensor de temperatura y presión BMP280 y 128 pantallas OLED de 64 píxeles) y un módulo GPS USB que envía datos a través de UART. GPIO se probó en el LED de la placa, funciona, y la conexión de la pantalla SPI parecía innecesaria en esta etapa de complicación.



Resultó ser una aplicación bastante compacta y simple, el código fuente se puede ver en Github.

No profundizaré en los fragmentos de código, pero trataré de describir en una descripción general cómo funciona la aplicación.

Los controladores de todos los dispositivos se realizan en forma de procesos gen_server, esto es conveniente porque le permite agregar parámetros adicionales y, a veces, el estado del dispositivo al estado. Esto último se puede ver en el ejemplo de uart_gps : los datos del UART llegan de forma asincrónica, son analizados por el analizador NMEA0183 y los resultados se escriben en el estado del proceso, desde donde se obtienen por solicitud.

El ciclo principal de la aplicación se describe en el módulo gps_temp_display : cada segundo, el proceso lee los datos del GPS y solicita el estado de temperatura y presión de BMP280 y los muestra en la pantalla OLED. En aras del interés, puede mirar la pantalla y los controladores de sensor BMP280 : todo resultó de manera bastante sucinta, 150-170 líneas por módulo.

En general, la tarea anterior (escribir el código del controlador, combinar todo en una sola aplicación, ensamblar y probar) tomó aproximadamente cuatro tardes, dos horas en promedio, es decir estrictamente hablando, un par de días hábiles. Personalmente, me parece que este es un buen indicador para tratar de usar Erlang en aplicaciones integradas más complejas y serias que no requieren restricciones estrictas en tiempo real.

Por supuesto, nuestros intentos de utilizar Erlang para sistemas integrados no son los únicos. Hay varios proyectos interesantes a este respecto:

Source: https://habr.com/ru/post/es417245/


All Articles