
Não há limite para a perfeição. Parece que tudo funcionou bem, pequenos bugs e assim por diante foram corrigidos.
Agora, em primeiro lugar, vou falar sobre os problemas que encontrei desde o artigo anterior e, em segundo lugar, sobre as soluções que contribuíram para o status atual do projeto.
→ Artigo sobre a versão anterior
Designações
bobaos
- npm para interagir com o BAOS 83x usando o UART. Retorna dados brutos. Usado em todos os outros módulos listados abaixo.
bdsd.sock
- um script para trabalhar com objetos KNX. Armazena uma lista de pontos de dados, converte valores ao enviar / receber. Do DPT1 para verdadeiro / falso, do DPT9 para flutuar. Também escuta no Unix Socket para receber solicitações de outros processos.
bobaos.pub
é uma nova versão usando redis
para comunicação entre processos.
KNX /
- um objeto de comunicação do módulo BAOS 83x configurado no ETS ao qual o endereço de grupo (a) corresponde (ou não). Nas versões atuais do ferro, o valor máximo é 1000.
Desafio
A tarefa principal é a mesma da versão anterior. Há apenas uma conexão com a porta serial. Existem muitos scripts trabalhando com o KNX. Além disso, eu queria implementar a comunicação entre processos. I.e. para que não apenas um processo bdsd.sock
o soquete, mas cada script em execução possa enviar e receber solicitações.
Idéia
Uma ideia surgiu em minha mente para criar meu próprio mediador de mensagens no node.js, sobre soquetes do Unix, ao qual os clientes se conectariam, assinariam tópicos e receberiam / enviariam mensagens de acordo com o código escrito neles. Eu sabia que já havia soluções prontas, sobre as quais apenas os preguiçosos não ouviram recentemente, estudaram, mas a ideia de tomar minha própria decisão era obsessiva.
E, como resultado, o serviço é lançado.
Escreveu um criador de logs que envia mensagens para o tópico. Os assinantes recebem e são livres para fazer qualquer coisa, ou melhor, o que é prescrito. Conveniente - os logs de várias fontes podem ser visualizados em uma saída do console.
Eu escrevi e publiquei o pacote bobaos.pub no npm, que, diferente do bdsd.sock, não cria mais um arquivo de soquete, mas se conecta a um broker. À primeira vista, tudo funciona como deveria.
O problema
Em seguida, executei um script que envia periodicamente solicitações para o barramento KNX da noite para o dia. Ao acordar de manhã, pelo piscar dos LEDs sinalizando o envio / transmissão de dados, percebi que algo estava errado. As mensagens não chegaram a tempo. Descobri que o intermediário de mensagens auto-escritas ocupava quase todos os 512 MB de RAM disponíveis (do BeagleBoard Black). Trabalhos adicionais com o nodejs confirmaram que a memória é o ponto fraco dos scripts js.
Solução
Como resultado, foi decidido mudar de sockets Unix genéricos para Redis (a propósito, ele também sabe como trabalhar com eles). Talvez valesse a pena resolver a memória, encontrar vazamentos, mas eu queria correr mais rápido.
bobaos significa comunicação UART com quebra de mensagens no FT1.2, temos comunicação síncrona. I.e. <..--..>
. O módulo bobaos, responsável pela comunicação, armazena todas as solicitações na matriz, retira-a por sua vez, envia-a para o UART e, com a resposta recebida, permite a promessa responsável por essa solicitação.
Você pode seguir este caminho: o serviço escuta o canal PIS / SUB redis, aceita solicitações, envia para o KNX. Nesse caso, a carga na fila de solicitações fica com o módulo js bobaos
. Para implementação, você precisa escrever um módulo simples inscrito em um canal e converter mensagens usando o método JSON.parse()
. Além disso, este módulo pode ser usado em outros scripts.
Outra opção que acabei redis
: use um gerenciador de tarefas existente sobre os redis
. Existem várias opções feitas na bee-queue
.
→ Sob o capô
Descreve como a bee-queue
funciona. Se você implementar esta biblioteca para outras linguagens de programação, poderá criar bibliotecas clientes para bobaos
dessa maneira.
Na segunda versão, todos os pedidos são armazenados em listas redis
, retirados por sua vez e enviados para a porta serial.
Além disso, a reescrita da versão anterior segue, mas eu já armazeno todos os dados em pontos de dados no banco de dados redis
. O único inconveniente que sinto é que todas as solicitações são assíncronas, portanto, obter uma matriz de valores já é um pouco mais difícil do que apenas acessar a matriz.
Pequenas otimizações foram feitas.
Se anteriormente havia métodos separados, getValue/getValues/readValue/readValues/setValue/setValues
, agora getValue/readValue/setValues
aceitam um valor único e uma matriz.
O método getValue([id1, id2, ...])
na versão anterior enviou uma solicitação para a porta serial para cada ponto de dados. Mas há uma oportunidade de enviar uma solicitação para vários valores. Limitações - o tamanho da resposta deve ser igual a BufferSize
, o máximo - 250 bytes; também é impossível ir além do número de objetos; nas versões atuais dos módulos BAOS 83x - 1000.
Os comprimentos dos valores são conhecidos, o cabeçalho também. Além disso, um algoritmo bastante simples com ciclos while e aguarda =)
- Classifique a matriz, exclua os elementos duplicados, se houver. nós obtemos uma matriz
idUniq
. - Iniciamos o ciclo
i < idUniq.length
, no qual fazemos o seguinte:
a) start: idUniq[i]
, pois consideramos o número máximo de valores que podemos obter. Por exemplo, se todos os objetos são do tipo DPT1 / DPT5 (1 byte), podemos obter valores no valor de 48. Há uma observação: se, por exemplo, tivermos configurado os objetos #[1, 2, 3, 10, 20]
, ao consultar GetDatapointValue.Req(1, 30)
, a resposta retornará zero valor de byte único, mesmo para pontos de dados inexistentes [4, 5, 6, ..., 30]
.
b) A contagem ocorre em um novo ciclo j < i + max
(onde max
é 50, ou, se for próximo de 1000, e no máximo 1000 - id + 1
, para 990 será 11, para 999 - 2), se no processo de contagem encontrarmos elementos da matriz da consulta original, atribuímos o índice i
variável i
.
c) Se no ciclo j
comprimento calculado exceder o tamanho máximo do buffer, formaremos o elemento do cartão de consulta {start: start, number: number}
, soltá-lo em uma matriz separada, aumentar a variável i
(ou atribuir o índice ao idUniq
encontrado na matriz), interromper ciclo j
, ambos os ciclos são reiniciados.
Assim, formamos vários pedidos de bobaos
. Por exemplo, se você enviar uma solicitação getValue([1, 2, 3, 40, 48, 49, 50, 100, 998, 999, 1000])
, as solicitações poderão ser as seguintes para um 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
Isso poderia ser feito de maneira diferente:
{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
Haveria tantos pedidos, menos dados. Mas parei na primeira opção, já que os valores obtidos são armazenados no banco de dados redis
, respectivamente, eles podem ser obtidos usando o método getStoredValue
, que tento usar com mais frequência do que getValue
, que envia dados pela porta serial.
Uma fila separada é criada para os métodos de serviço ping/get sdk state/reset
. Portanto, se algo estiver errado com a comunicação na porta serial (o contador de quadros se perdeu, etc.) e a execução for interrompida em alguma tarefa, você poderá enviar uma solicitação de reset
em outra fila e, consequentemente, reiniciar o sdk
.
Lado do cliente - bobaos.sub
Para controlar os pontos de dados do KNX nos scripts do usuário, o módulo bobaos.sub
pode ser usado.
O exemplo a seguir abrange todas as funções de um 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); });
A interface da linha de comandos foi reescrita. Sobre como eu o implementei no seguinte artigo:
→ Escrevendo CLI no NodeJS
As equipes se tornaram mais curtas, mais claras e mais funcionais.
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==]
Posfácio
O resultado foi um sistema de trabalho estável. Redis
como back-end funciona de maneira estável. Durante o desenvolvimento, muitos cones foram embalados. Mas o processo de aprendizagem é tal que às vezes é inevitável. Pela minha experiência, observo que os processos do nodejs
consomem bastante RAM (20 MB no início) e pode haver vazamentos. Para automação residencial, isso é crítico - porque o script deve funcionar constantemente e, se cresce cada vez mais com o tempo, então, em um determinado momento, pode ocupar todo o espaço. Portanto, você deve escrever cuidadosamente os scripts, entender como o coletor de lixo funciona e manter tudo sob controle.
A documentação da atualização pode ser encontrada aqui .
No próximo artigo, falarei sobre como usar redis
e bee-queue
fiz um serviço para acessórios de software.
UPD: bobaoskit - acessórios, dnssd e WebSocket
Ficarei feliz com qualquer feedback.