O MQTT (Message Queuing Telemetry Transport) é um protocolo para comunicação de mensagens entre dispositivos como sensores e computadores móveis. Neste projeto, integraremos o modelo anterior, que se baseava na comunicação UART entre a placa raspberry e o node MCU, com a comunicação MQTT para uma interface de usuário remota.
Segue abaixo a estrutura de diretórios do projeto
├── dashboard
│ └── chart.js
│ └── index.html
│ └── mqttws31.min.js
├── nodemcu/uart
│ └── uart.ino
├── README.md
└── rpi
├── display.s
├── examples
│ └── countdown.c
├── lib
│ ├── fileio.s
│ ├── gpio.s
│ ├── lcd.s
│ └── utils.s
├── LICENSE
├── makefile
├── system.c
└── uart
└── uart.c
cloud-sensors/nodemcu/ - Obtém os valores registrados pelos sensores, realiza a comunicação via UART com a SBC (Raspberry) e MQTT com a interface remota.
Possui a macro open_file para abertura de arquivos. Recebe no R0, o descritor do arquivo aberto, no R1, o modo de abertura do arquivo.
Possui a macro nanosleep para fazer o programa parar durante o tempo específicado. R0 é um ponteiro para quantidade de segundos e R1 é um ponteiro para quantidade de nanossegundos.
Possui macros para configurar pinos como entrada e saída, alterar o nível lógico no modo de saída e ler o nível lógico em determinado pino. A sessão de pinos tem seu array configurado da seguinte maneira:
Biblioteca principal para o controle do LCD
Programa principal para execução do contador. O valor do contador fica registrado em R1, e as flags para pausar/continuar e reiniciar contagem, estão nos registradores R6 e R5, respectivamente
Para facilitar a construção do programa, existe um makefile dentro da pasta rpi, onde é possível executar:
$ make csystem
Para construção do executável. Logo em seguida basta utilizar:
$ sudo ./system
para executar o programa
countdown: counter
example: cexample
uart: cuart
system: csystem
counter: display.o
gcc -o display display.o
display.o: display.s lib/lcd.o
as -o display.o display.s
lib/lcd.o: lib/utils.o lib/gpio.o lib/fileio.o
as -o lib/lcd.o lib/lcd.s
lib/fileio.o: lib/fileio.s
as -o lib/fileio.o lib/fileio.s
lib/gpio.o: lib/gpio.s
as -o lib/gpio.o lib/gpio.s
lib/utils.o: lib/utils.s
as -o lib/utils.o lib/utils.s
uart/uart.o: uart/uart.c
gcc -c uart/uart.c -lwiringPi
cexample: examples/countdown.c lib/lcd.s
gcc -o countdown examples/countdown.c lib/lcd.s
cuart: uart/uart.c lib/lcd.s
gcc -o uartx uart/uart.c lib/lcd.s -lwiringPi
csystem: system.c lib/lcd.s
gcc -o system system.c lib/lcd.s -lwiringPi
Abaixo está presente os dispositivos utilizados, suas características e documentação utilizada para desenvolvimento do projeto
A plataforma NodeMCU é uma placa de desenvolvimento que combina o chip ESP8266, uma interface usb-serial e um regulador de tensão 3.3V. Mais dados sobre sua documentação podem ser encontrados aqui.
Alguns pinos utilizados na NodeMCU estão listados na tabela abaixo:
Pino | Descrição |
---|---|
D0 | Sensor Digital 1 |
D1 | Sensor Digital 2 |
A0 | Sensor Analógico 1 |
TX | Envio comunicação serial |
RX | Recebimento comunicação serial |
Além de contar com os pinos para comunicação UART com a Node MCU, agora utilizam-se 3 botões para a interface local humana. A função destes botões será explicada posteriormente.
Pino | GPIO | Descrição |
---|---|---|
8 | 14 | TX |
10 | 15 | RX |
- | 5 | BTN |
- | 19 | BTN |
- | 26 | BTN |
Para troca de informações entre os dispositivos, foram definidos comandos. Cada informação é enviada com 1 byte, onde os três bits menos significativos indicam um comando:
B2 | B1 | B0 | Descrição |
---|---|---|---|
0 | 0 | 1 | Solicita status da NodeMCU |
0 | 1 | 0 | Solicita status do sensor |
0 | 1 | 1 | Solicita valor do sensor |
Os bits mais significativos B7-B3, indicam qual sensor vai ser executado o comando: 0 - 31 (32 sensores).
Em relação ao projeto anterior, manteve-se a conexão via UART entre a SBC e a node MCU. O que mudou foi a adição de novos dados sendo passados e o protocolo MQTT estabelecido entre a Node e a interface local, intermediada pelo broker do laboratório.
Os valores dos sensores e seus status foram armazenados em dois vetores de 32 posições. Uma vez que a informação está presente, é recuperada de maneira genérica pela estrutura da informação, onde é separado informação e sensor associado. Desta forma, caso se queira adicionar um novo sensor, basta garantir que a informação vai estar presente na posição escolhida para o mesmo.
A NodeMCU fica constantemente ouvindo o seu canal RX, e toda vez que recebe um pedido, efetua os procedimentos anteriores para retornar a resposta.
Utilizando a implementação para o UART feita no projeto anterior como biblioteca, aqui a nova função para a raspberry é assumir o papel de interface humana local. O usuário, a partir de botões da placa, poderá alterar o sensor, modo de funcionamento e tempo do intervalo. Ao mesmo tempo, todas estas mudanças são registradas no visor LCD.
Inicialmente, define-se 4 constantes. Cada constante representa um comando que será interpretado pela NodeMCU. MODE_SENSOR (0) é o modo em que será exibido o valor atual do sensor escolhido e também possibilitará que o usuário possa alternar entre os possíveis sensores. MODE_FREQUENCY (1) é o modo em que o usuário pode alterar o valor atual do tempo de intervalo da comunicação e esta alteração afetará a interface remota, além de, claro, visualizar no LCD o valor atual vingente. No modo MODE_MCU_STATUS (2), mostra o status de comunicação da NodeMCU. MODE_SENSOR_STATUS (3) tal qual o modo anterior, mostra o status de conexão, porém do sensor específico.
Abaixo, há outras constantes e variáveis úteis para facilitação de entendimento do código. TOTAL_SENSOR representa o número totais de sensores da comunicação, ou seja 1 analógico e 2 digitais. A variável current_screen inicializa o programa em modo de tela de valor de sensor. Em current_frequency a frequência de tempo inicial é definida como 5.
Na imagem acima, ilustra-se 4 funções que definem a lógica por trás da seleção de sensores e alteração da frequência. Numa comunicação UART, só é possível enviar 8 bits de informação. Dito isso, pré-definiu-se que os 5 primeiros bits representarão o número do sensor ou o valor da frequência, e os 3 últimos o a seleção de comandos. Assim, como tem-se 5 bits reservados a sensor e frequência, o usuário pode selecionar do sensor 00000 (0) ao sensor 11111 (31), totalizando 32 sensores. Da mesma maneira, como os mesmos 5 bits são reservados a frequência, o usuário pode alterar a frequência de 0 a 31 segundos. Como dito anteriormente, os 3 últimos bits são responsáveis por decidir o comando. 001 para status do nodemcu, 010 para status do sensor, 011 para o valor do sensor e 100 para frequência.
Iniciando pela primeira função get_mcu_status_code, aqui meramente é retornado o valor 1 ou, em binário de 8 bits, XXXXX001, sendo os 5 primeiros bits em don't care já que o número do sensor ou a frequência são irrelevantes para a tela de status da node. Em sequência, get_sensor_status_code recebe o valor do sensor atual, faz um deslocalamento em 3 bits (multiplica por 8) e soma com 2. Dessa forma, se, por exemplo, o usuário deseja obter o status do sensor 2, a função retornará (28)+2, ou seja, 18, que, em binário de 8 bits é representado por 00010 (Sensor 2) 010 (Comando 2). As funções get_read_sensor_code e get_set_frequency_code seguem exatamente a mesma lógica anterior, a primeira recebe o sensor novamente, multiplica por 8 e soma com 3 (Teria-se o binário 00010 011 pegando o mesmo sensor do exemplo anterior) e a segunda recebe a frequência, também multiplica por 3 e adiciona ao comando 4, dessa forma, caso o usuário deseje estabelecer a velocidade como 22 segundos, por exemplo, teria-se o valor 228 + 4, ou 180, que em binário é representado como 10110 (22) 100 (4).
Já na função principal, a comunicação UART é inicializada e os 3 botões de interface local humana são pinados. O botão de pino 05 é resposável por alterar a tela atual. Os pinos 19 e 26 são responsáveis por, respectivamente, voltar ou avançar na seleção de sensores ou diminuir e aumentar o valor da frequência. Em sequência cria-se algumas variáveis para ajudar a legibilidade do código.
Entrando em loop, o sistema lê o valor atual do pino 05, caso esteja pressionado, a váriavel current_screen é incrementada. Como só existem 4 telas possíveis, o valor da tela atual retorna ao ponto inicial (0) quando o usuário pressionar o botão 05 pela 4° vez. Após isso, gera-se um delay de 500 milissegundos para evitar o fator boucing do botão. Neste ponto, nota-se que o botão 05 é o principal, pois ele decide em que modo a interface local irá operar, podendo alterar a função dos dois botões de avanço e retardo ou, até mesmo, inutilizá-los em telas que não é necessário alterar sensor ou frequência.
Em sequência, verifica-se se o valor da tela atual corresponde ao modo de sensor (0), caso sim, a condição é atendida e é lido os valores dos botões 19 e 26. Caso o botão 19 tenha sido pressionado, o sensor atual (armazenado na variável current_sensor é decrementado, caso seja o botão 26, o sensor atual é incrementado. A constante TOTAL_SENSOR é útil para, na operação de divisão e resto, forçar a contagem variar entre 0, 1 e 2 (3 sensores). Feito isso, independente se foi incrementado ou não, obtém-se o comando (cmd) chamando a função get_read_sensor_code e passando o sensor atual. Finalmente, o comando obtido é passado pela UART chamando a função send_data.
Caso o valor da tela corresponda ao modo de frequência (1), novamente lê-se o valor dos botões 19 e 26. Da mesma forma que 19 volta ao sensor anterior, aqui 19 diminui o valor da frequência atual, enquanto 26 aumenta este valor. As divisões e operações de resto com 0 e 31 servem para manter a contagem alternando de 0 segundos a 31 segundos. Da mesma forma, o valor da frequência é enviado para a função get_set_frequency_code e obtém-se o comando, o qual é enviado para a node mcu.
Uma vez na tela de status da node (2), os botões de avanço e recuo são inúteis, bastando retornar diretamente o valor da função get_mcu_status_code. O status foi definido de forma fixa como 1.
Por fim, na tela de status de sensor (3), repete-se a mesma lógica utilizada no modo de sensor, botão 19 para recuo, 26 para avanço, chama a função específica para esta operação e retorna o comando a node mcu através da UART.
A interface remota foi feita em linguagem de marcação HTML e programação em javascript.
Inicialmente se define os parâmetros da conexão em MQTT. Os valores para host, porta, usuário e senha foram pré-definidos no broker do laboratório LEDS. Após isso cria-se 2 tópicos, um para atualização dos sensores (lê (subscriber) o valor e os nomes do sensores publicados (publisher) pela Node MCU) e outro para alterar a frequência (publisher).
A constante onMessageArrived é utilizada para obter os dados dos sensores da Node MCU. Inicialmente, obtém-se o nome do tópico e status da conexão. Caso o tópico corresponda ao tópico de atualização e a conexão esteja habilitada, a condição é atendida. Em seguida, obtém o valor da data atual, exclui-se a primeira data registrada na conexão (a mais antiga) e atualiza o histórico para as 10 mais novas (com a atual). A informação da conexão é parseada para o formato json, de modo a utilizar a função map e obter um array de objetos contendo o atributo label (nome do sensor) e data (valor do sensor). Por fim, limpa-se o gráfico em removeCharts e adiciona os novos dados em addData. Tais funções serão explicadas posteriormente.
A constante onConnect é útil para estabelecer a interface remota como subscriber do tópico de atualização. Utiliza-se a biblioteca PAHO para utilizar as funções do protocolo MQTT. Aqui chama-se a função mqtt.subscribe para inscrever-se no tópico de atualização e lança uma mensagem caso haja conexão.
Por fim, estabelece-se a conexão MQTT passando os parâmetros de host e porta definidos anteriormente. Em caso de uma mensagem recebida, chama-se a constante onMessageArrived para obter os nomes e valores de sensores explicados anteriormente. Por fim, a conexão é efetivamente iniciada em mqtt.connect, passando os parâmetros de usuários e senha. Caso haja conexão, a constante onConnect é chamada para inscrever a interface ao tópico.
Neste ponto, cria-se na interface uma caixa de input. Nela, o usuário digita o valor da frequência que será enviado ao node mcu. Em seguida, e logo abaixo na interface, cria-se um canvas, corpo de HTML que permite edição de javascript. Neste canvas é implementado o gráfico do histórico de valor dos sensores.
Na área de script, adiciona um evento de listening para o formulário de frequência. A cada vez que o usuário atualiza o valor vingente, o programa resgata tal valor e o publica no tópico de frequência utilizando a função _mqtt.send.
Em sequência, cria-se o gráfico utilizando a biblioteca Chart na área do canvas. Inicialmente, o gráfico é preenchido com valores aleatórios, apenas para ocupar a tela enquanto os valores da conexão não são recebidos.
Por fim, têm-se as duas funções citadas anteriormente. A função addData substitui os labels do gráfico pelo vetor que armazena os nomes de todos os sensores conectados a node mcu e atualiza os dados pelos respectivos valores temporais de cada sensor. Já a função removeData apenas esvazia os registros do gráfico, esvaziando os vetores.
- Na pasta rpi/ execute:
$ make csystem
- Em seguida execute o programa
$ sudo ./system
- Na pasta nodemcu/ abra o arquivo uart.ino na Arduino IDE:
- Configure as bibliotecas do NodeMCU
- Descarregue o código na pltaforma
Para a Raspberry Pi se comunicar com a NodeMCU é preciso receber uma palavra chave da NodeMCU indicando uma inicialização. Devido ao fato que ao reiniciar a NodeMCU a mesma emite vários dados aleatórios nas saídas da UART.
- Inicializar o programa na Raspberry Pi
- Descarregar programa na NodeMCU
- No terminal, deve ser imprimido a mensagem: "Secret code found", indicando que a comunicação foi estabelecida
- Após o Teste 1 executado, o sistema está pronto pra execução.
- Inserir comando: 11, para transferir a informação presente no sensor de índice 0.
- Caso queira inserir o valor presente na entrada D0 da NodeMCU, basta realizar:
SENSOR_VALUE[0] = digitalRead(D0)
na função de loop da NodeMCU - Caso haja um nível lógico ativo, a resposta deve ser 1. Caso contrário, a resposta deve ser 0.
- Com a interface local, fora da tela de frequência, acessar a interface remota e configurar a frequência para 5.
- As atualizações, em ambas interfaces devem ocorrer a cada 5 segundos.
- Mudar o valor de um sensor, esperar a mudança.
- Mudar novamente o valor e contar quanto tempo passará até a atualização.
- O novo valor deve ser atualizado após 5 segundos.
- Acessar interface remota.
- Alterar valor de algum sensor.
- Observar o valor alterado na interface remota.
O protótipo construído é um sistema digital utilizando plataformas de desenvolvimento IoT, em que se pode adicionar até 32 sensores analógicos e/ou digitais e exibir as respectivas informações em um display de LCD.
Devido a limitações da biblioteca, a quantidade de informações transferidas é limitada, pois a mesma está sendo quebrada em diversos pacotes. Isso gera um atraso na transferência, devido ao tempo de publicação das mensagens. como o caso do i2c;
A NodeMCU não possui relógio. Então, as informações não são enviadas com a hora em que elas foram coletadas. Diferentemente da Raspberry Pi que conta com este recurso, facilitando assim a organização temporal dos dados.
A NodeMCU não é capaz de reter grandes quantidades de informação em memória, logo, se houvesse a necessidade de guardar um histórico mais longo dos sensores, poderia se tornar inviável