En 2017, comencé a escribir un proyecto en nodejs, una implementación del protocolo Weinzierl ObjectServer para acceder a los valores KNX. Durante el proceso de escritura, estudiamos: trabajar con protocolos binarios, presentar datos, trabajar con sockets (sockets unix en particular), trabajar con la base de datos redis y canales pub / sub.
El proyecto ha alcanzado una versión estable. En este momento, lentamente elijo otros idiomas, en particular Dart and Flutter como su aplicación. En el estante espolvoreado sin acción comprada en el momento del manual del estudiante G. Schildt.
Un pensamiento persistente para reescribir el proyecto en C se instaló en mi cabeza. Considero las opciones Go, Rust, repeler otras construcciones sintácticas. No hay forma de comenzar, la idea se pospone por un tiempo.
En mayo de este año, decidí mirar el lenguaje D, por alguna razón convencido de que la letra D significa dinámico. Durante mucho tiempo me pregunté dónde y por qué este pensamiento estaba en mi cabeza, así que no encontré una respuesta. PERO esto ya no es importante, ya que me dejé llevar por la transcripción durante todo el verano.
La esencia del proyecto.
Los módulos KNX BAOS 830/832/838 están conectados a través de UART a una computadora, el protocolo ObjectServer está envuelto en FT1.2. La aplicación establece una conexión con /dev/ttyXXX
, procesa los datos entrantes, envía los bytes de la solicitud del usuario convertidos al canal PUB / SUB a la misma cola desde mensajes JSON, o a la cola de trabajos basada en listas Redis (para nodejs, las colas se implementan usando el paquete de cola de abejas )
queue.on("job", data => {
Dinamismo
JSON en js es una cosa nativa; no tenía idea de cómo ocurre el procesamiento en lenguajes estáticamente escritos. Al final resultó que, una pequeña diferencia. Por ejemplo, tome el método get value
. Como argumentos, toma un número: el número de punto de fecha o una matriz de números.
En js, se realizan verificaciones:
if (Array.isArray(payload)) {
Esencialmente lo mismo en D:
if (payload.type() == JSONType.integer) {
Por alguna razón, en el momento de Rust, una consideración, fue la falta de comprensión de trabajar con JSON lo que me retrasó. Otro punto relacionado con el dinamismo: las matrices. En js, te acostumbras al hecho de que es suficiente llamar al método push
para agregar un elemento. En C, el dinamismo se implementa mediante la asignación manual de memoria, pero realmente no quería escalar allí. Dlang, como resultó, admite matrices dinámicas.
ubyte[] res;
Los datos UART entrantes en js se convirtieron a Object
. Para estos fines, las estructuras, las enumeraciones con valores y las uniones son excelentes en D.
enum OS_Services { unknown, GetServerItemReq = 0x01, GetServerItemRes = 0x81, SetServerItemReq = 0x02, SetServerItemRes = 0x82,
Con un mensaje entrante:
ubyte mainService = data.read!ubyte(); ubyte subService = data.read!ubyte(); try { if (mainService == OS_MainService) { switch(subService) { case OS_Services.GetServerItemRes: result.direction = OS_MessageDirection.response; result.service= OS_Services.GetServerItemRes; result.success = true; result.server_items = _processServerItemRes(data); break; case OS_Services.SetServerItemRes: result.direction = OS_MessageDirection.response;
En js, almacené los valores de bytes en una matriz, con los datos entrantes hice una búsqueda y devolví una cadena con el nombre del servicio. Las estructuras, enumeraciones y asociaciones se ven más estrictas.
Trabajar con conjuntos de datos de bytes
Node.js Me gusta la abstracción de Buffer
. Por ejemplo: es conveniente realizar la conversión de dos bytes a un entero sin signo utilizando el readUInt16BE(offset)
, para escribir - writeUInt16BE(value, offset)
, buffers utilizados activamente para trabajar con el protocolo binario. Para dlang, inicialmente comencé paquetes de repositorio lanudo a algo similar. La respuesta se encontró en la biblioteca estándar std.bitmanip
. Para enteros sin signo de 2 bytes de longitud: ushort start = data.read!ushort()
, para escribir: result.write!ushort(start, 2);
donde el segundo argumento es el desplazamiento.
EE, promesas, asíncrono / espera.
La peor parte fue programar sin un EventEmitter
. En node.js, las funciones de escucha simplemente se registran y, en un evento, se llaman. Por lo tanto, uno no tiene que pensar mucho. Los paquetes dlang tinylis y serialport
(dependencias de mi aplicación) tienen métodos sin bloqueo para procesar mensajes. La solución es simple: por ahora, es cierto recibir mensajes de puerto serie y pub / subcanal a su vez. En el caso de una solicitud de usuario entrante al canal pub / sub, el programa debe enviar un mensaje al puerto serie, obtener el resultado y enviar al usuario nuevamente a pub / sub. Se decidió realizar los métodos para el bloqueo de solicitudes en serie.
while(!(_responseReceived || _resetInd || _interrupted)) { try { processIncomingData(); processIncomingInterrupts(); if (_resetInd || _interrupted) { _response.success = false; _response.service = OS_Services.unknown; _response.error = Errors.interrupted; _responseReceived = true; _ackReceived = true; }
En un ciclo while, los datos se sondean mediante el método sin bloqueo processIncomingData()
. También se proporciona la probabilidad de que el módulo KNX se pueda reiniciar (desconectado y reconectado al bus o software KNX). Además, el controlador processIncomingInterrupts()
comprueba el servicio pub / sub channel para una solicitud de reset
. Sin promesas ni funciones asincrónicas, a diferencia de las implementaciones js anteriores. Tuve que pensar en la estructura del programa (es decir, la secuencia de llamadas a funciones), pero, debido a la ausencia de abstracciones innecesarias, se hizo más fácil programar. De hecho, cuando await someAsyncMethod
se llama a await someAsyncMethod
en el código js, la función asincrónica se llama bloqueo y pasa por el bucle de eventos. La posibilidad misma del lenguaje es buena, pero puedes prescindir de ella.
Las diferencias
Cola de trabajo La implementación de node.js utiliza el paquete bee-queue
para este propósito. En una implementación en D, las solicitudes se envían solo a través de pub / sub.
De lo contrario, todo es casi idéntico.
La versión compilada consume 10 veces menos RAM, lo que puede ser importante para las computadoras de una sola placa.
Compilación
La compilación se realizó usando ldc en la plataforma aarch64.
Para instalar ldc:
curl -fsS https://dlang.org/install.sh | bash -s ldc
Se ensambló una placa base, que constaba de tres componentes principales: NanoPi Neo Core2 como computadora, el módulo KNX BAOS 830 para la comunicación con el bus KNX y Silvertel Ag9205 para alimentación PoE, en la que se realizó la programación.
Conclusión
No juzgaré qué idioma es mejor o peor. Para cada uno lo suyo: js es ideal para estudiar, el nivel de abstracciones (promesas, emisores) le permite construir fácil y rápidamente la estructura de la aplicación. Me acerqué a la implementación de dlang con un plan claro y memorizado para un año y medio, qué hacer. Cuando sabe qué datos deben procesarse y cómo, la escritura estática no da miedo. Los métodos sin bloqueo le permiten organizar un ciclo de trabajo. Este fue mi primer trabajo en D, un trabajo fascinante e informativo.
En cuanto a salir de la zona de confort (como se indica en el título): en mi caso, el miedo tenía grandes ojos, lo que durante mucho tiempo me impidió intentar algo diferente a nodejs.
Los códigos fuente están abiertos y se pueden encontrar en github.com/dobaos/dobaos