Node-RED - Banco de Dados

Node-RED - Banco de Dados

Introdução

Os sistemas supervisórios devem ser capazes de armazenar os dados históricos da planta de forma permanente. Normalmente, estes dados são valores de variáveis, horário de logins de usuário, alertas e quaisquer outros eventos relevantes ocorridos na planta monitoradas, sendo importantes para registrar todo o histórico da planta.

Uma das formas mais eficiente, confiável e segura de armazenar estes dados é utilizando softwares de bancos de dados (do inglês, Data Base, ou DB). Além de viabilizarem um armazenamento seguro, estes softwares disponibilizam ferramentas para recuperar e manipular os dados de forma eficiente.

Para a nossa prática, iremos interagir o Node-RED com um servidor de banco de dados do Mongo-DB, software gratuito e muito utilizado devido a sua versatilidade e fácil integração com Javascript, permitindo a escrita e leitura de dados diretamente em formato JSON.

Na prática, o software do Mongo-DB disponibiliza um servidor de banco de dados e um serviço que permite receber chamadas em uma das portas da máquina que o executa. Assim, qualquer software terceiro que deseja utilizar este serviço deve interagir com o Mongo-DB através de seu endereço URI (i.e., https://<IP>:<PORTA>). Por padrão, o Mongo-DB utiliza a porta 27017.

Organização dos dados

Um único servidor Mongo-DB é capaz de armazenar vários bancos de dados, que são unidades de armazenamento independentes identificados por um nome único naquele servidor. O identificador deve ser uma string única naquele servidor. Recomenda-se definir seu nome somente com letras minúsculas, sem espaços ou caracteres especiais.

Cada banco de dados pode ter diversas coletâneas (do inglês, collection), que são similares ao conceito de planilhas do excel, permitindo registrar diversas colunas de diferentes tipos de dados, cada coluna podendo receber várias entradas (linhas/rows) de dados. Cada coletânea também é identificada por uma string única cujo nome deve ser definido de maneira similar aos identificadores dos bancos de dados.

Exemplo 1·Coletânea do Mongo-DB

A tabela a seguir é um exemplo de dados registrados em uma coletânea do Mongo-DB:

_idtimestamp-da-leituravalor-do-sensor
001173746413432223.4
002173746415415023.8
003173746416630224.2
004173746418175424.6
005173746419639624.4

No exemplo acima, a coletânea possui três colunas de dados (_id, timestamp-da-leitura e valor-do-sensor) e cinco linhas de dados (rows). Os nomes das colunas podem ser livremente definidos.

Nota

O Mongo-DB exige a existência da coluna _id, que é um identificador único para cada linha. Se você não definir esta coluna/valor explicitamente, o Mongo-DB fará isto automaticamente para você.

Nota

Cada linha de dados inserida em uma coletânea deve seguir a estrutura de dados já definidas pelas colunas. O Mongo-DB permite que tipos de dados diferentes sejam enviados para uma mesma coluna, o que porém é uma prática não recomendada a menos que seja explicitamente assim desejado.

Instalando o Mongo-DB

O Mongo-DB pode ser instalado diretamente no Sistema Operacional. Os links a seguir descrevem como fazê-lo no Linux, macOS e Windows.).

Entretanto, para quem já tem o Docker instalado na máquina, basta executar o seguinte comando para levantar um container executando o servidor do Mongo-DB:

docker run -d -p 27017:27017 --name mongo-container -v mongo-data:/data/db --network net-nodered mongo:latest

Os parâmetros do comando acima são:

  • docker run: Invoca o docker e pede para ele rodar um container a partir de uma imagem.
  • -d: Executa em modo "detached", i.e., o container será executado em segundo plano, não "prendendo" o terminal atual.
  • -p 27017:27017: Mapeia a porta 27017 o computador host na porta 27017 do container, i.e., chamadas na porta 27017 do host serão direcionadas para a porta 27017 do container. Caso queira disponibilizar o Mongo-DB em outra porta do host, pode-se alterar o primeiro valor deste argumento. Por exemplo, para disponibilizar o BD na porta 30301, pode-se alterar este argumento para -p 30301:27017.
  • --name mongo-container: Nome do container que será executado. Este nome pode ser alterado para o que preferir.
  • -v mongo-data:/data/db: Cria um volume de armazenamento permanente no host chamado mongo-data e remapeia este volume no caminho /data/db do container. Este parâmetro garante que os dados armazenados pelo Mongo-DB serão permanentes enquanto o volume mongo-data existir no host, mesmo que o container do servidor seja deletado. Este parâmetro é essencial na criação de servidores de banco de dados.
  • --network net-nodered: conecta o container à rede net-nodered. Este parâmetro garante que o container do banco de dados estará conectado à mesma rede virtual que o container do Node-RED.
  • mongo:latest: O container será criado utilizando a imagem mongo em sua versão latest (i.e., a última versão estável disponível). Note que o docker faz automaticamente o download desta imagem a partir do dockerhub, que é seu servidor de imagens oficial.

Após levantar o servidor, pode-se testar se o Mongo-DB está disponível utilizando o comando nmap:

bash
nmap -p <PORTA-DO-MONGO> <IP>

Caso esteja executando o docker em sua própria máquina, pode substituir o parâmetro <IP> por localhost.

Dica

Existem softwares que permitem interagir com um banco de dados a fim de permitir visualizar e manipular os dados armazenados de maneira mais visual e interativa. Para o Mongo-DB recomenda-se o MongoDB Compass.

Interagindo o Node-RED com um servidor Mongo-DB

Para interagir com o servidor Mongo-DB, instale no Node-RED o módulo node-red-contrib-mongodb4, cujo manual pode ser encontrado neste link. Ao instalá-lo, irá aparecer na paleta o nó mongodb4, que permite interagir e realizar operações em um servidor Mongo-DB.

Para iniciar, arraste o nó mongodb4 para o flow e dê 2x cliques nele para abrir suas propriedades. A propriedade Connection permite criar uma conexão com um servidor em execução. Para isto, clique no símbolo de + e abrirá a janela Simple Connection URI. Como configurações mínimas, defina os seguintes parâmetros:

  • Hostname: Insira o IP da máquina que está executando o servidor.
  • Port: Insira a porta em que o serviço está disponibilizado.
  • Database: O nome do banco de dados que será utilizado. Caso ainda não exista, o nó irá criá-lo no servidor.
Atenção

Caso esteja rodando o Node-RED e o Mongo-DB ambos em containers docker, fique atento pois o IP será o próprio nome do container do Mongo-DB (que o Docker automaticamente resolve para o endereço do mesmo em sua rede interna) e a porta será a porta interna do Mongo-DB (i.e., 27017).

Clique então em Add para criar a conexão com este banco de dados. A janela irá retornar para as propriedades do nó mongodb4 com a propriedade Connection já definida com a conexão recém criada. Pronto! assim já é possível interagir com o banco de dados.

Nota

A criação da conexão com cada banco de dados deve ser feita somente uma única vez. Caso adicione novos nós do mongodb4 em seu flow, basta apenas definir a propriedade Connection com a conexão já criada.

Mensagem básica de interação

Para utilizar o nó mongodb4, deve-se enviar uma msg na entrada de seu bloco. É obrigatório que cada mensagem contenha os seguintes campos:

  • msg.collection: String com o nome da coletânea (tabela) de trabalho.
  • msg.operation: Tipo da operação que será realizada (find, findOne, insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, aggregate).
  • msg.payload: Conteúdo em função da operação que será realizada.
Exemplo 2·Mensagem básica para mongodb4

Exemplo de função que cria a estrutura básica de interação com o banco de dados:

javascript
msg = {}; // limpa completamente a mensagem
msg.collection = "nome-da-coletanea";
msg.operation = "tipo-de-operacao";
msg.payload = [ /* argumentos posicionais */ ];
return msg;

Msg.payload

O msg.payload é sempre um array posicional que mapeia diretamente para os argumentos do driver oficial do MongoDB para Node.js:

PosiçãoConteúdo típico
[0]Filtro / query
[1]Opções (e.g., sort, limit, projection, ...) ou documento de update
[2]Opções extras (ex.: { upsert: true })

Operadores de filtro mais comuns

OperadorSignificadoExemplo
$eqIgual a{ valor: { $eq: 1 } }
$neDiferente de{ status: { $ne: "erro" } }
$gtMaior que{ valor: { $gt: 0.5 } }
$gteMaior ou igual{ valor: { $gte: 0.5 } }
$ltMenor que{ valor: { $lt: 0.9 } }
$lteMenor ou igual{ valor: { $lte: 0.9 } }
$inDentro de um array{ sensor: { $in: ["S1","S2"] } }
$ninFora de um array{ sensor: { $nin: ["S3"] } }
$andE lógico{ $and: [ {a: 1}, {b: 2} ] }
$orOu lógico{ $or: [ {a: 1}, {b: 2} ] }

Opções de leitura mais comuns (2º elemento do array)

OpçãoTipoDescrição
sortobjectOrdenação: 1 crescente, -1 decrescente. Ex.: { timestamp: -1 }
limitnumberNúmero máximo de documentos retornados
skipnumberPula N documentos — útil para paginação
projectionobjectSeleciona campos: 1 inclui, 0 exclui
maxTimeMSnumberTimeout máximo da operação em milissegundos

Operações

A seguir seguem as definições e exemplos de utilização de cada uma das possíveis operações com o Mongo-DB.

insertOne

Insere uma única linha de dados na collection. Os dados devem ser organizados como um JSON, sendo a chave o nome da coluna e seu valor o dado a ser inserido naquela linha.

Sua sintaxe é:

javascript
msg.payload = [ {nome-coluna-1: <dado-a-ser-inserido>, 
                  nome-coluna-2: <dado-a-ser-inserido>
              } ];
Exemplo 3·insertOne

Exemplo de código típico no armazenamento de dados de sensores com marcação de hora da leitura.

javascript
msg = {} // Limpa a mensagem por completo.
msg.collection = "sensores-temperatura";
msg.operation = "insertOne";

// Dados a serem inseridos no banco de dados
msg.payload = [{
  sensor:    "sensor-12", // uma string que descreve o nome do sensor que coletou o dado
  timestamp: new Date().getTime(), // recebe um número com o timestamp atual
  value:     Math.random()*100 // cria um número aleatório entre 0 e 100 para mockar o dado do sensor
}];

return msg;

Ao injetar esta mensagem, colete a saída do nó mongodb4 em um nó Debug e note que é retornada uma mensagem indicando sucesso (ou não) na operação e o identificador da entrada de dados recém criada.

insertMany

Insere múltiplos documentos no banco de dados em uma única operação. Esta operação é mais eficiente do que chamar várias vezes insertOne, sendo preferida caso já haja uma grande quantidade de linhas disponíveis a serem salvas.

A sintaxe é:

javascript
msg.payload = [ arrayDeDocumentos, opções ];
Exemplo 4·insertMany

O exemplo a seguir realiza a inserção de vários registros em uma coletânea de uma só vez.

javascript
msg = {} // Limpa a mensagem por completo.
msg.collection = "sensores-temperatura";
msg.operation = "insertMany";
msg.payload = [[{ sensor: "sensor-1", timestamp: new Date().getTime(), value: Math.random()*100 },
    { sensor: "sensor-2", timestamp: new Date().getTime(), value: Math.random()*100 },
    { sensor: "sensor-3", timestamp: new Date().getTime(), value: Math.random()*100 }
    ]]; // note que há um array de entradas dentro do array que, caso tivesse, traria os filtros e opções nos outros elementos.
return msg;

find

Retorna um array contendo todos os documentos que satisfazem o filtro enviado.

Sintaxe:

javascript
msg.payload = [ <filtro>, <opções> ];
Exemplo 5·find

Para retornar todos os registros de uma collection:

javascript
// Todos os documentos da collection
msg.collection = "sensores-temperatura";
msg.operation = "find";
msg.payload = [ {}, {} ];
return msg;

Para retornar uma entrada buscando-se por seu identificador:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "findOne";
msg.payload = [
    { _id: new mongodb.ObjectId("6a15e0a54ea48a43ab568637") }
];
return msg;
Atenção

No código de exemplo acima, perceba que o identificador é um objeto próprio da bibliteca do mongodb. Para utilizar a classe ObjectId, é necessário adicionar o module name com valor mongodb e importar como mongodb na aba Setup das propriedades do bloco de função.

Para retornar os últimos 10 registros:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "find";
msg.payload = [
  {},
  { sort: { timestamp: -1 }, // `-1` para ordenação decrescente
    limit: 10 } // 
];
return msg;

Para retornar com filtro de intervalo de valores + projeção de campos:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "find";
msg.payload = [
  { value: { $gte: 60, $lte: 95 } },
  {
    sort: { timestamp: 1 }, // `1` para ordenação crescente
    limit: 50,  // quantidade máxima de retorno
    projection: { sensor: 1, timestamp: 1, value: 1, _id: 0 } // retorna apenas `timestamp`, `value` e nome do sensor
  }
];
return msg;

Em todos os casos, o retorno em msg.payload será um array com os registros que satisfazem os filtros definidos.

findOne

Retorna apenas o primeiro documento que satisfaz o filtro. Esta operação é mais eficiente que find quando deseja-se encontrar somente um registro.

Sua sintaxe é:

javascript
msg.payload = [ filtro, opções ];
Exemplo 6·findOne

Retorna o último elemento registrado na coletânea ordenado por timestamp.

javascript
msg.collection = "sensores-temperatura";
msg.operation = "findOne";
msg.payload = [{}, { sort: { timestamp: -1 } }
];
return msg;

Retorna o primeiro registro da coletânea que tenha a chave sensor igual a sensor-1.

javascript
msg.collection = "sensores-temperatura";
msg.operation = "findOne";
msg.payload = [{sensor: "sensor-1"}, {}
];
return msg;

Em qualquer caso, a variável msg.payload receberá um único objeto (ou null se nada for encontrado).

updateOne

Atualiza o primeiro registro que satisfaz o filtro.

Sintaxe:

javascript
msg.payload = [ filtro, update, opções ];

Os operadores de update mais comuns são:

OperadorAção
$setDefine/substitui campos
$unsetRemove campos
$incIncrementa valor numérico
$pushAdiciona item a um array
$pullRemove item de um array
Exemplo 7·updateOne

Alterando o nome de sensor de uma entrada específica utilizando seu identificador:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "updateOne";
msg.payload = [
    { _id: new mongodb.ObjectId("6a15e0a54ea48a43ab568635") },
    { $set: { sensor: "sensor-24" } }
]; 
return msg;

Remove o campo value do primeiro registro que tenha a chave sensor igual a sensor-1:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "updateOne";
msg.payload = [
    { sensor: "sensor-1" },
    { $unset: { value: "" } }
];
return msg;

updateMany

Atualiza todos os registros que satisfazem o filtro. Possui a mesma sintaxe do updateOne, mas afeta múltiplos documentos ao mesmo tempo.

Exemplo 8·updateMany

Altera para sensor-01 o valor da chave sensor em todas as entradas onde esta tiver o valor sensor-1.

javascript
msg.collection = "sensores-temperatura";
msg.operation = "updateMany";
msg.payload = [
    { sensor: "sensor-1" },
    { $set: { sensor: "sensor-01" } }
];
return msg;

Adiciona a chave planta com o valor Ouro Branco a todos os registros da coletânea.

javascript
msg.collection = "sensores-temperatura";
msg.operation = "updateMany";
msg.payload = [
    { },
    { $set: { planta: "Ouro Branco" } }
];
return msg;


deleteOne

Remove o primeiro documento que satisfaz o filtro.

Sua sintaxe é somente:

javascript
msg.payload = [ filtro ];
Exemplo 9·deleteOne

Deleta o primeiro registro da coletânea ordenado por timestamp:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "deleteOne";
msg.payload = [
    {  },
    { sort: {timestamp: 1} }
];
return msg;

Deleta um registro específico por seu ID:

javscript
msg.collection = "sensores-temperatura";
msg.operation = "deleteOne";
msg.payload = [{ _id: new mongodb.ObjectId("6a15e0a54ea48a43ab568632")  }, 
                {  }];
return msg;

deleteMany

Remove todos os documentos que satisfazem o filtro.

Atenção

Caso deixe o filtro {} vazio, toda a coletânea será deletada.

Sintaxe da mensagem:

javascript
msg.payload = [ filtro ];
Exemplo 10·deleteMany

Deleta todos os registros mais antigos que 10 minutos

javascript
msg.collection = "sensores-temperatura";
msg.operation = "deleteMany";
let dezMinutosAtras = new Date().getTime() - ( 10 * 60 * 1000);
msg.payload = [
    { timestamp: { $lt: dezMinutosAtras } }
];
return msg;

Deleta todos os registros do sensor 2:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "deleteMany";
msg.payload = [
    { sensor: "sensor-2" }
];
return msg;

aggregate

Executa um pipeline de agregação, permitindo encadear estágios de filtragen, transformação, agrupamento e cálculo sobre os dados. É a operação mais poderosa do MongoDB.

Sua sintaxe é:

javascript
msg.payload = [[ estágio1, estágio2, ... ]];  // array dentro de array

Os estágios mais comuns são:

EstágioFunção
$matchFiltra documentos (equivalente ao find)
$sortOrdena documentos
$limitLimita quantidade de documentos
$skipPula N documentos
$groupAgrupa e calcula métricas
$projectSeleciona/transforma campos
$countConta documentos
$unwindDesdobra arrays em documentos separados

Os acumuladores para o estágio $group são:

AcumuladorFunção
$avgMédia
$sumSoma (ou contagem com $sum: 1)
$maxValor máximo
$minValor mínimo
$firstPrimeiro valor do grupo
$lastÚltimo valor do grupo
Exemplo 11·aggregate

Calcula a média, máximo e mínimo da chave value para os 30 últimos registros da coletânea:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "aggregate";
msg.payload = [[
    { $sort: { timestamp: -1 } },
    { $limit: 20 },
    {
        $group: {
            _id: null,
            media: { $avg: "$value" },
            maximo: { $max: "$value" },
            minimo: { $min: "$value" },
        }
    }
]];
return msg;

Calcula a média de leitura agrupado por sensor:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "aggregate";
msg.payload = [[
    {
        $group: {
            _id: "$sensor", // cria um grupo onde o _id é o valor da chave sensor dos dados de entrada
            media: { $avg: "$value" },
            count: { $sum: 1 }
        }
    },
    { $sort: { _id: 1 } }
]];
return msg;

Calcula a média por hora do dia:

javascript
msg.collection = "sensores-temperatura";
msg.operation = "aggregate";
msg.payload = [[
    {
        $group: {
            _id: {
                $dateToString: {
                    format: "%Y-%m-%dT%H:00", // note que o marcador vai até horas, o resto está zerado
                    date: { $toDate: "$timestamp" }
                }
            },
            media: { $avg: "$value" },
            count: { $sum: 1 }
        }
    },
    { $sort: { _id: 1 } }
]];
return msg;

Calcula a média e momento da última leitura dos últimos 50 registros de sensor-1 com valor maior que 70 e retorna somente as chaves calculadas media e ultimo, sem _id.

javascript
msg.collection = "sensores-temperatura";
msg.operation = "aggregate";
msg.payload = [[
    { $match: { sensor: "sensor-1", value: { $gt: 70 } } },
    { $sort: { timestamp: -1 } },
    { $limit: 50 },
    {
        $group: {
            _id: null,
            media: { $avg: "$value" },
            ultimo: { $first: "$timestamp" } // note que ordenação por timestamp está descrescente
        }
    },
    { $project: { _id: 0, media: 1, ultimo: 1 } }
]];
return msg;

Problema 1·Registro do monitor da Estação Espacial Internacional

Continuando o Problema da aula anterior (Link), considere que a aplicação colete os dados da estação a cada segundo.

Implemente nesta aplicação o registro em banco de dados de uma coletânea com a seguinte estrutura:

timestamplongitudelatitudedistancia-percorrida
xxxxxxxxx0

Os registros deve ser inseridos no banco de dados na mesma taxa de aquisição dos dados da ISS.

Além de tudo o que já foi implementado neste projeto até aqui, o seu programa deve ser capaz de:

  • Recuperar e printar no debug todos os registros do banco de dados.
  • Recuperar e printar no debug os últimos 30 registros, somente de timestamp, longitude e latitude.
  • Calcular a média dos campos longitude e latitude dos últimos 60 registros.
  • Deletar automaticamente todas as leituras mais antigas que 3 minutos.

Autor: FILIPE A. S. ROCHA

Publicado em 26 de maio de 2026· Atualizado em 26 de maio de 2026