Em 2017, comecei a escrever um projeto no nodejs - uma implementação do protocolo Weinzierl ObjectServer para acessar valores KNX. Durante o processo de escrita, estudamos: trabalhando com protocolos binários, apresentando dados, trabalhando com soquetes (soquetes unix em particular), trabalhando com banco de dados redis e canais pub / sub.
O projeto atingiu uma versão estável. Neste momento, eu lentamente escolho outras línguas, em particular Dart e Flutter como sua aplicação. Na prateleira, polvilhada sem ação adquirida no momento do manual do aluno G. Schildt.
Um pensamento persistente para reescrever o projeto em C se instalou na minha cabeça. Considero opções Go, Rust, repelindo outras construções sintáticas. Não há como começar, a idéia é adiada por um tempo.
Em maio deste ano, decidi examinar a linguagem D, por algum motivo convencido de que a letra D significa dinâmica. Eu me perguntei por um longo tempo onde e por que esse pensamento estava na minha cabeça, então não encontrei uma resposta. Mas isso não é mais importante, já que fui levado pela transcrição durante todo o verão.
A essência do projeto
Os módulos KNX BAOS 830/832/838 são conectados via UART a um computador, o protocolo ObjectServer é agrupado no FT1.2. O aplicativo estabelece uma conexão com /dev/ttyXXX
, processa os dados recebidos, envia os bytes da solicitação do usuário convertidos no canal PUB / SUB para a mesma fila de mensagens JSON ou para a fila de tarefas baseada nas listas Redis (para nodejs, as filas são implementadas usando o pacote bee-Queue )
queue.on("job", data => {
Dinamismo
JSON em js é uma coisa nativa; eu não tinha ideia de como o processamento ocorre em linguagens estaticamente tipadas. Como se viu, uma pequena diferença. Por exemplo, use o método get value
. Como argumentos, é necessário um número - o número do ponto da data ou uma matriz de números.
Em js, as verificações são realizadas:
if (Array.isArray(payload)) {
Essencialmente o mesmo em D:
if (payload.type() == JSONType.integer) {
Por alguma razão, na época de Rust, uma consideração, foi a falta de entendimento de trabalhar com JSON que me atrasou. Outro ponto relacionado ao dinamismo: matrizes. Em js, você se acostuma ao fato de que basta chamar o método push
para adicionar um elemento. Em C, o dinamismo é implementado pela alocação manual de memória, mas eu realmente não queria subir lá. Dlang, como se viu, suporta matrizes dinâmicas.
ubyte[] res;
Os dados UART recebidos em js foram convertidos em Object
. Para esses fins, estruturas, enumerações com valores e junções são ótimas em D
enum OS_Services { unknown, GetServerItemReq = 0x01, GetServerItemRes = 0x81, SetServerItemReq = 0x02, SetServerItemRes = 0x82,
Com uma mensagem recebida:
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;
Em js, eu armazenei os valores de bytes em uma matriz, com os dados recebidos, fiz uma pesquisa e retornei uma string com o nome do serviço. Estruturas, enumerações e associações parecem mais rígidas.
Trabalhar com matrizes de dados de bytes
Node.js Eu gosto da abstração do Buffer
. Por exemplo: é conveniente executar a conversão de dois bytes em um número inteiro não assinado usando o método readUInt16BE(offset)
, para escrever - writeUInt16BE(value, offset)
, buffers usados ativamente para trabalhar com o protocolo binário. Para o dlang, iniciei inicialmente os pacotes de repositório lanoso para algo semelhante. A resposta foi encontrada na biblioteca padrão std.bitmanip
. Para números inteiros não assinados com 2 bytes de comprimento: ushort start = data.read!ushort()
, para gravação: result.write!ushort(start, 2);
onde o segundo argumento é o deslocamento.
EE, promessas, assíncronas / aguardam.
A pior parte foi programar sem um EventEmitter
. No node.js, as funções de ouvinte são simplesmente registradas e, em um evento, são chamadas. Assim, não é preciso pensar muito. Os pacotes tinylis e serialport
dlang (dependências do meu aplicativo) têm métodos sem bloqueio para processar mensagens. A solução é simples: por enquanto, é verdade o recebimento de mensagens de porta serial e pub / sub canal por vez. No caso de uma solicitação de usuário recebida para o canal pub / sub, o programa deve enviar uma mensagem para a porta serial, obter o resultado e enviar o usuário de volta para pub / sub. Foi decidido fazer os métodos para o bloqueio de solicitações seriais.
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; }
Em um loop while, os dados são pesquisados pelo método non-blocking processIncomingData()
. A probabilidade de que o módulo KNX possa ser reinicializado (desconectado e reconectado ao barramento ou software KNX) também é fornecida. Além disso, o manipulador processIncomingInterrupts()
verifica o pub / sub canal de serviço em busca de uma solicitação de reset
. Nenhuma promessa ou função assíncrona, ao contrário das implementações js anteriores. Eu tive que pensar na estrutura do programa (ou seja, na seqüência de chamadas de função), mas, devido à ausência de abstrações desnecessárias, ficou mais fácil programar. De fato, quando await someAsyncMethod
é chamado no código js, a função assíncrona é chamada de bloqueio, passando pelo loop de eventos. A própria possibilidade de linguagem é boa, mas você pode ficar sem ela.
Diferenças
Fila de trabalhos. A implementação do node.js usa o pacote bee-queue
para esse fim. Em uma implementação em D, as solicitações são enviadas apenas através de pub / sub.
Caso contrário, tudo é quase idêntico.
A versão compilada consome 10 vezes menos RAM, o que pode ser importante para computadores de placa única.
Compilação
A compilação foi realizada usando ldc na plataforma aarch64.
Para instalar o ldc:
curl -fsS https://dlang.org/install.sh | bash -s ldc
Uma placa-mãe foi montada, composta por três componentes principais: NanoPi Neo Core2 como computador, módulo KNX BAOS 830 para comunicação com o barramento KNX e Silvertel Ag9205 para energia PoE, na qual a programação foi realizada.
Conclusão
Não julgarei qual idioma é melhor ou pior. Para cada um deles: js é ótimo para estudar, o nível de abstrações (promessas, emissores) permite que você construa fácil e rapidamente a estrutura do aplicativo. Abordei a implementação em dlang com um plano claro e memorizado por um ano e meio, o que fazer. Quando você sabe quais dados precisam ser processados e como, a digitação estática não é assustadora. Métodos sem bloqueio permitem organizar um ciclo de trabalho. Este foi o meu primeiro trabalho em D, um trabalho fascinante e informativo.
Quanto a deixar a zona de conforto (como indicado no título): no meu caso, o medo tinha olhos grandes, o que por um longo tempo me impediu de tentar algo diferente de nodejs.
Os códigos-fonte estão abertos e podem ser encontrados em github.com/dobaos/dobaos