bobaos.pub - KNX TP / UART, Raspberry Pi y Redis


No hay límite para la perfección. Parece que todo funcionó bien, se corrigieron errores menores, etc.


Ahora le contaré, en primer lugar, sobre los problemas que he encontrado durante todo el tiempo que ha pasado desde el artículo anterior y, en segundo lugar, sobre las soluciones que contribuyeron al estado actual del proyecto.


Artículo sobre la versión anterior


Designaciones


bobaos - módulo npm para interactuar con BAOS 83x usando UART. Devuelve datos sin procesar. Se utiliza en todos los demás módulos enumerados a continuación.


bdsd.sock : un script para trabajar con objetos KNX. Almacena una lista de puntos de datos, convierte valores al enviar / recibir. De DPT1 a verdadero / falso, de DPT9 a flotante. También escucha en Unix Socket para recibir solicitudes de otros procesos.


bobaos.pub es una nueva versión que usa redis para la comunicación entre procesos.
KNX / : un objeto de comunicación del módulo BAOS 83x configurado en ETS al que corresponde (o no) la dirección de grupo (a). En las versiones actuales de hierro, la cantidad máxima es 1000.


Desafío


La tarea principal es la misma que la versión anterior. Solo hay una conexión al puerto serie. Hay muchos scripts que funcionan con KNX. Además de esto, quería implementar la comunicación entre procesos. Es decir para que no solo un proceso bdsd.sock el socket, sino que cada script en ejecución pueda enviar y recibir solicitudes.


Idea


Nació una idea en mi cabeza para hacer mi propio intermediario de mensajes en node.js en la parte superior de los sockets de Unix, a los que los clientes se conectarían, suscribirían a temas y recibirían / ​​enviarían mensajes de acuerdo con el código prescrito en ellos. Sabía que ya había soluciones preparadas de las que solo los perezosos no habían oído hablar recientemente, estudiaron, pero la idea de tomar mi propia decisión era obsesiva.


Y como resultado, se lanza el servicio.


Escribió un registrador que envía mensajes al tema. Los suscriptores reciben y son libres de hacer cualquier cosa, o más bien, lo que se prescribe. Conveniente: los registros de varias fuentes se pueden ver en una salida de consola.


Escribí y publiqué el paquete bobaos.pub en npm, que, a diferencia de bdsd.sock, ya no crea un archivo de socket, sino que se conecta a un intermediario. A primera vista, todo funciona como debería.


El problema


Luego ejecuté un script que envía periódicamente solicitudes al bus KNX durante la noche. Al despertarme por la mañana, al parpadear los LED que indicaban el envío / transmisión de datos, me di cuenta de que algo andaba mal. Los mensajes no llegaron a tiempo. Encontré que el agente de mensajes autoescrito tomó casi todos los 512 MB de RAM disponibles (de BeagleBoard Black). El trabajo posterior con nodejs confirmó que la memoria es el punto débil de los scripts js.


Solución


Como resultado, se decidió cambiar de sockets genéricos de Unix a Redis (por cierto, él también sabe cómo trabajar con ellos). Quizás valió la pena ordenar la memoria, encontrar fugas, pero quería correr más rápido.


bobaos significa comunicación UART con ajuste de mensajes en FT1.2, tenemos comunicación sincrónica. Es decir <..--..> . El módulo bobaos, que es responsable de la comunicación, almacena todas las solicitudes en la matriz, lo extrae a su vez, lo envía a UART y, con la respuesta entrante, resuelve la promesa responsable de esta solicitud.


Puede seguir este camino: el servicio escucha el canal PUB / SUB de redis, acepta solicitudes, lo envía a KNX. En este caso, la carga en la cola de solicitudes recae en el módulo js bobaos . Para la implementación, debe escribir un módulo simple suscrito a un canal y convertir mensajes utilizando el método JSON.parse() . Además, este módulo se puede utilizar en otros scripts.


Otra opción que terminé redis : usar un administrador de tareas existente encima de redis . Hay varias opciones hechas en bee-queue .


Debajo del capó


Describe cómo funciona la bee-queue . Si implementa esta biblioteca para otros lenguajes de programación, puede crear bibliotecas cliente para bobaos de esta manera.


En la segunda versión, todas las solicitudes se almacenan en listas de redis , extraídas a su vez y enviadas al puerto serie.


Además, sigue la reescritura de la versión anterior, pero ya almaceno todos los datos en puntos de datos en la base de datos redis . El único inconveniente que experimento es que todas las solicitudes son asíncronas, por lo que obtener una matriz de valores es un poco más difícil que simplemente acceder a la matriz.


Se han realizado optimizaciones menores.


Si antes había métodos separados getValue/getValues/readValue/readValues/setValue/setValues , ahora getValue/readValue/setValues toma un valor único y una matriz.


El método getValue([id1, id2, ...]) en la versión anterior envió una solicitud al puerto serie para cada punto de datos. Pero existe la oportunidad de enviar una solicitud de varios valores. Limitaciones: el tamaño de la respuesta debe ser igual a BufferSize , el máximo: 250 bytes; También es imposible ir más allá del número de objetos; para las versiones actuales de los módulos BAOS 83x - 1000.


Las longitudes de los valores son conocidas, el encabezado también. Además, un algoritmo bastante simple con while y esperar ciclos =)


  1. Ordenar la matriz, eliminar elementos duplicados, si los hay. obtenemos una matriz idUniq .
  2. Comenzamos el ciclo i < idUniq.length , en el que hacemos lo siguiente:
    a) start: idUniq[i] , para ello consideramos el número máximo de valores que podemos obtener. Por ejemplo, si todos los objetos son del tipo DPT1 / DPT5 (1 byte), entonces podemos obtener 48 valores. Hay una observación: si, por ejemplo, hemos configurado objetos #[1, 2, 3, 10, 20] , entonces la consulta GetDatapointValue.Req(1, 30) devolverá cero valores de un solo byte en respuesta incluso para puntos de datos inexistentes [4, 5, 6, ..., 30] .
    b) El recuento se lleva a cabo en un nuevo ciclo j < i + max (donde max es 50 o, si está cerca de 1000, entonces máximo 1000 - id + 1 , para 990 será 11, para 999 - 2), si durante el cálculo nos encontramos elementos de la matriz de la consulta original, luego asigne el índice i variable i .
    c) Si en el ciclo j longitud calculada excede la longitud máxima del búfer, entonces formamos el elemento de la tarjeta de consulta {start: start, number: number} , lo idUniq en una matriz separada, aumentamos la variable i (o asignamos el índice al idUniq encontrado en la matriz), interrumpimos ciclo j , ambos ciclos se reinician.

Por lo tanto, formamos varias solicitudes de bobaos . Por ejemplo, si envía una solicitud getValue([1, 2, 3, 40, 48, 49, 50, 100, 998, 999, 1000]) , las solicitudes pueden ser las siguientes para un caso especial:


 {start: 1, number: 48}, // 1, 2, 3, 40, 48 {start: 49, number: 48}, // 49, 50 {start: 100, number: 48}, // 100 {start: 998, number: 3} // 998, 999, 1000 

Se podría hacer de otra manera:


 {start: 1, number: 48}, // 1, 2, 3, 40, 48 {start: 49, number: 2}, // 49, 50 {start: 100, number: 1}, // 100 {start: 998, number: 3} // 998, 999, 1000 

Habría tantas solicitudes, menos datos. Pero me detuve en la primera opción, ya que los valores obtenidos se almacenan en la redis datos de redis , respectivamente, se pueden obtener utilizando el método getStoredValue , que trato de usar con más frecuencia que getValue , que envía datos a través del puerto serie.


Se crea una cola separada para los métodos de servicio ping/get sdk state/reset . Por lo tanto, si algo está mal con la comunicación en el puerto serie (el contador de trama se desvió, etc.) y la ejecución se detiene en alguna tarea, puede enviar una solicitud de reset en otra cola y, en consecuencia, reiniciar sdk .


Lado del cliente - bobaos.sub


Para controlar los puntos de datos KNX en los scripts de usuario, se puede usar el módulo bobaos.sub .


El siguiente ejemplo cubre todas las funciones de un módulo:


 const BobaosSub = require("bobaos.sub"); //     : // redis:   url // request_channel: "bobaos_req"  , // service_channel: "bobaos_service"  , // broadcast_channel: "bobaos_bcast"   let my = BobaosSub(); my.on("connect", _ => { console.log("connected to ipc, still not subscribed to channels"); }); my.on("ready", async _ => { try { console.log("hello, friend"); console.log("ping:", await my.ping()); console.log("get sdk state:", await my.getSdkState()); console.log("get value:", await my.getValue([1, 107, 106])); console.log("get stored value:", await my.getValue([1, 107, 106])); console.log("get server item:", await my.getServerItem([1, 2, 3])); console.log("set value:", await my.setValue({id: 103, value: 0})); console.log("read value:", await my.readValue([1, 103, 104, 105])); console.log("get programming mode:", await my.getProgrammingMode()); console.log("set programming mode:", await my.setProgrammingMode(true)); console.log("get parameter byte", await my.getParameterByte([1, 2, 3, 4])); console.log("reset", await my.reset()); } catch(e) { console.log("err", e.message); } }); my.on("datapoint value", payload => { //   ,  payload    ,    //     Array.isArray(payload) console.log("broadcasted datapoint value: ", payload); }); my.on("server item", payload => { //   ,  payload    ,    //     Array.isArray(payload) console.log("broadcasted server item: ", payload); }); my.on("sdk state", payload => { console.log("broadcasted sdk state: ", payload); }); 

bobaos.tool


La interfaz de línea de comando ha sido reescrita. Sobre cómo lo implementé el siguiente artículo:


Escribir CLI en NodeJS


Los equipos se han vuelto más cortos, más claros y más funcionales.


 bobaos> progmode ? BAOS module in programming mode: false bobaos> progmode 1 BAOS module in programming mode: true bobaos> progmode false BAOS module in programming mode: false bobaos> description 1 2 3 #1: length = 2, dpt = dpt9, prio: low, flags: [C-WTU] #2: length = 1, dpt = dpt1, prio: low, flags: [C-WT-] #3: length = 1, dpt = dpt1, prio: low, flags: [C-WT-] bobaos> set 2: 0 20:27:06:239, id: 2, value: false, raw: [AA==] bobaos> set [2: 0, 3: false] 20:28:48:586, id: 2, value: false, raw: [AA==] 20:28:48:592, id: 3, value: false, raw: [AA==] 

Epílogo


El resultado fue un sistema de trabajo estable. Redis como backend funciona de manera estable. Durante el desarrollo, se embalaron muchos conos. Pero el proceso de aprendizaje es tal que a veces es inevitable. Según mi experiencia, observo que los procesos de nodejs consumen bastante RAM (20 MB al inicio) y puede haber fugas. Para la automatización del hogar, esto es crítico, porque el script debería funcionar constantemente y, si crece más y más con el tiempo, en cierto momento puede ocupar todo el espacio. Por lo tanto, debe escribir cuidadosamente los scripts, comprender cómo funciona el recolector de basura y mantener todo bajo control.


La documentación de actualización se puede encontrar aquí .


En el próximo artículo, hablaré sobre cómo al usar redis y bee-queue un servicio para accesorios de software.


UPD: bobaoskit - accesorios, dnssd y WebSocket


Estaré encantado de cualquier comentario.

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


All Articles