Integrando Laravel com o protocolo MQTT para comunicação entre dispositivos

Projeto de comunicação entre dois dispositivos ESP8266 e Raspberrypi4. Laravel irá funcionar como servidor e receptor de dados de temperatura e umidade coletados com o DHT11.

Postado em 15 outubro 2022

Atualizado em 15 outubro 2022



Introdução do projeto MQTT

Projeto de comunicação entre dispositivos usando o protocolo MQTT. Esse projeto irá usar o sensor DHT11 para medir a umidade e a temperatura do ambiente. Os dados coletados pelo sensor serão enviados pelo NodeMCU ESP8266 através do protocolo citado e serão recebidos por um Raspberrypi que irá registrar esses dados no banco de dados local.

O acesso e visualização aos dados serão proporcionados pelo raspberrypi que terá a porta 80 aberta para tais fins.

Hardwares utilizados

Os seguintes hardwares serão utilizados:

Dispositivo Papel
Raspberrypi 4 Receptor e disponibilizador de dados
ESP8266 Responsável pelo envio dos dados
DHT11 Sensor de temperatura e umidade
LED Apaga quando não há energia fluindo através do circuito
Fonte de energia Fornece eletricidade
Cabos e resistores Circuito

Formação dos componentes

A conexão entre os dispositivos irá ser realizada da seguinte forma:

MQTT
Recibo/registro de dados
Raspberrypi 4
Coleta/envio de dados
DHT11
ESP8266

Principais hardwares:
dispositivos utilizados no projeto

Design do circuito dos hardwares

O circuito será estabelecido da seguinte forma:
circuito do projeto mqtt com laravel

Ferramentas utilizadas

O ESP8266 será programado em linguagem C, enquanto o programa no raspberrypi será programado com o PHP. Para facilitar e economizar tempo de configuração, o programa receptor irá ser desenvolvido com o Laravel.

Linguagem Papel
Linguagem C Programação ESP8266
PHP Programação software raspberrypi
Javascript Frontend
Laravel Auxiliar a programação no PHP
Apache Servidor raspberrypi
MariaDB Banco de dados raspberrypi
Composer Gerenciamento de pacotes PHP
Node.js Requerimento para NPM
npm Gerenciamento de pacotes Javascript

O sistema operacional utilizado no raspberry é o citado abaixo:

pipipi@raspberrypi:~ $cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 11 (bullseye)"
NAME="Raspbian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=raspbian
ID_LIKE=debian

As versões das ferramentas utilizadas nesse projeto são:

pipipi@raspberrypi:~ $ apachectl -v
Server version: Apache/2.4.54 (Raspbian)
Server built: 2022-06-09T04:26:43
pipipi@raspberrypi:~ $ php --version
PHP 8.1.11 (cli) (built: Sep 29 2022 22:17:15) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.11, Copyright (c) Zend Technologies
with Zend OPcache v8.1.11, Copyright (c), by Zend Technologies
pipipi@raspberrypi:~ $ mysql --version
mysql  Ver 15.1 Distrib 10.5.15-MariaDB, for debian-linux-gnueabihf (armv8l) using  EditLine wrapper
pipipi@raspberrypi:~ $ composer --version
Composer version 2.4.2 2022-09-14 16:11:15
pipipi@raspberrypi:~ $ node -v
v14.20.1
pipipi@raspberrypi:~ $ npm -v
6.14.17

Configurando o ESP8266

O ESP8266 irá conectar-se a internet e publicará os dados coletados no canal do MQTT em formato JSON.

Como funciona o protocolo MQTT

O protocolo MQTT permite o envio e o recebimento de dados através de um canal.

Nesse projeto, o ESP8266 enviará dados para o “MainChannel”. O aplicativo do raspberrypi irá se inscrever nesse canal para receber os dados.

MQTT
MQTT
ESP8266
MainChannel
Laravel

Importando as bibliotecas e inicializando as variáveis

O ESP8266 utilizará as seguintes bibliotecas:

  • ESP8266WiFi
  • PubSubClient
  • dht
  • ArduinoJson

Importação de bibliotecas, definição e inicialização de variáveis:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <dht.h>
#include <ArduinoJson.h>

#define DHT11_PIN D1
IPAddress local_IP(192, 168, 11, 184);
IPAddress gateway(192, 168, 11, 1);
IPAddress subnet(255, 255, 255, 0);

dht DHT;
const char* ssid = "xxxxxxxxxx";
const char* password = "xxxxxxxxxx";
const char* mqtt_server = "broker.emqx.io";

WiFiClient espClient;
PubSubClient client(espClient);

Configurando a conexão com a internet

O ESP8266 irá continuar tentando se conectar com a internet até ter sucesso. O endereço IP do ESP8266 na rede interna será fixo.

void setupWifi() {
    delay(100);
   
    WiFi.begin(ssid, password);
    while(WiFi.status() != WL_CONNECTED) {
	    delay(500);
    }

    randomSeed(micros());
    if (!WiFi.config(local_IP, gateway, subnet)) {
	    Serial.println("STA Failed to configure");
    }
}

Conexão com o canal de envio de dados

Os dados captados pelo DHT11 serão obtidos através do pino definido acima. Esses dados serão armazenados em estrutura JSON, sendo enviados através do canal “MainChannel”.

void sendMQTTMsg() {
    if (!client.connected()) {
	    reconnect();
    }

    client.loop();
    int chk = DHT.read11(DHT11_PIN);

    StaticJsonDocument<256> staticJsonDocument;
    staticJsonDocument["temperature"] = DHT.temperature;
    staticJsonDocument["humidity"] = DHT.humidity;
    
    char requestBody[128];
    serializeJson(staticJsonDocument, requestBody);
    client.publish("MainChannel", requestBody);
}

Invocação de funções

Uma vez que as funções necessárias já foram definidas, serão invocadas.

void setup() {
    Serial.begin(115200);
    setupWifi();
    sendMQTTMsg();
    delay(10000);
    ESP.deepSleep(30e6);
}

void loop() {}

O ESP8266 irá entrar em modo de hibernação após enviar os dados a cada 30 segundos para economizar energia.

Configurando o Raspberrypi

O Raspberrypi irá ser o receptor de dados. Responsável por armazenar e exibir os dados recebidos.

Requisitos para o desenvolvimento do projeto em laravel

  • Apache
  • PHP
  • Composer
  • Node.js
  • Npm

Desenvolvimento projeto em laravel

Criação de projeto usando composer.

composer create-project laravel/laravel mqtt-web-server

Instalação de repositório para as comunicações em MQTT. Repositório MQTT dedicado ao laravel será instalado.

composer require php-mqtt/laravel-client
php artisan vendor:publish --provider="PhpMqtt\Client\MqttClientServiceProvider" --tag="config"

Após publicar o arquivo de configuração acima, trocamos a conexão do MQTT para conexão privada.

// config/mqtt-client.php
...
return [
	'default_connection' => 'private',
	'connections' => [
		'private' => [
			'host' => "broker.emqx.io",
			'port' => 1883,
		],
	...
	]
]

Implementado arquivos necessários para manusear os dados recebidos

Os dados irão ser gravados no banco de dados e serão também usados para visualização. Por isso criaremos todos os arquivos necessários com um comando:

php artisan model:Message -a

O comando acima irá criar os seguintes arquivos:

  • Controller
  • Model
  • Migration
  • Factory
  • Seed

Apenas uma porção desses arquivos serão utilizados.

A estrutura da tabela do banco de dados será bastante simples, possuindo apenas 5 colunas.

// arquivo migration
public function up()
{
    Schema::create('messages', function (Blueprint $table) {
	    $table->id();
	    $table->string('topic');
	    $table->text('content');
	    $table->timestamps();
    });
}

Criando comandos para rodar em plano de fundo no laravel

Uma vez que as configurações de conexão do MQTT foram estabelecidas, criamos um comando no laravel.

php artisan make:command ReceiveMessage

Com o comando acima, um arquivo será criado. Nesse arquivo, adicionamos o algoritmo para receber mensagens do ESP8266 através do canal “MainChannel”.

public function handle()
{
    $mqtt = MQTT::connection();
    $mqtt->subscribe('MainChannel', function (string $topic, string $message) use ($mqtt) {
	    $message =  Message::create([
		    'topic' => $topic,
		    'content' => $message
	    ]);

	    if ($message->exists()) {
		    $mqtt->interrupt();
	    }
    }, 1);
    
    $mqtt->loop(true);
    $mqtt->disconnect();
    
    return Command::SUCCESS;
}

Certificamos se o registro foi adicionado ao banco de dados e interrompemos a conexão.

Configurando o task scheduling do laravel para invocar comandos em um determinado período de tempo

Laravel fornece ferramentas para invocar diversos comandos em certos períodos de tempo.

Adicionaremos o comando criado acima e invocaremos a cada minuto.

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
	$schedule->command('receive:message')->everyMinute();
}

Em seguida, testaremos usando o seguinte comando:

php artisan schedule:work

Caso não haja problemas, será exibido logs contendo informação de sucesso.

2022-10-08 23:00:00 Running ['artisan' receive:message] ...... 27,718ms DONE
⇂ '/usr/bin/php8.1' 'artisan' receive:message > '/dev/null' 2>&1

Executando o task scheduling do laravel em plano de fundo

Queremos que o comando acima seja invocado automaticamente sem a nossa intervenção. Para fazer isso, utilizaremos o supervisor.

Para instalar o supervisor é necessário acessar o raspberry. Aqui, o acesso será feito remotamente usando o SSH.

ssh [email protected]

Instalação do supervisor:

sudo apt-get install supervisor

Após a instalação do supervisor, a configuração da conexão em plano de fundo será realizada. O supervisor irá executar o task scheduling do laravel em segundo plano.

[program:mqtt]
process_name=%(program_name)s_%(process_num)02d
directory=/var/www/html/mqtt-web-server
command=php artisan schedule:work
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=pipipi
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/mqtt-web-server/storage/logs/worker.log
stopwaitsecs=3600

Uma vez que a configuração foi efetuada, atualizamos e executamos o supervisor.

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start mqtt:*

O resultado do comando será gravado no arquivo definido no arquivo de configuração do supervisor.

Registros no banco de dados

Após algum tempo, se verificarmos o banco de dados teremos os registros abaixo:

MariaDB [mqtt]> select * from messages order by created_at desc limit 10;
+------+-------------+----------------------------------+---------------------+---------------------+
| id   | topic       | content                          | created_at          | updated_at          |
+------+-------------+----------------------------------+---------------------+---------------------+
| 1554 | MainChannel | {"temperature":20,"humidity":70} | 2022-10-09 19:39:39 | 2022-10-09 19:39:39 |
| 1553 | MainChannel | {"temperature":20,"humidity":70} | 2022-10-09 19:38:10 | 2022-10-09 19:38:10 |
| 1552 | MainChannel | {"temperature":20,"humidity":70} | 2022-10-09 19:37:26 | 2022-10-09 19:37:26 |
| 1551 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:36:42 | 2022-10-09 19:36:42 |
| 1550 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:35:13 | 2022-10-09 19:35:13 |
| 1549 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:34:29 | 2022-10-09 19:34:29 |
| 1548 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:33:45 | 2022-10-09 19:33:45 |
| 1547 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:32:16 | 2022-10-09 19:32:16 |
| 1546 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:31:32 | 2022-10-09 19:31:32 |
| 1545 | MainChannel | {"temperature":21,"humidity":71} | 2022-10-09 19:30:03 | 2022-10-09 19:30:03 |
+------+-------------+----------------------------------+---------------------+---------------------+
10 rows in set (0.006 sec)

Se olharmos a data do registro é possível perceber que cada registro tem um valor com intervalo de 1 minuto de diferença.

A comunicação em MQTT termina aqui.

Criando uma interface web para a visualização dos dados gravados no banco de dados

Após certificar-se de que os dados estão sendo registrados a cada minuto, vamos criar uma interface básica para a visualização da informação.

Para agilizar, usaremos as seguintes extensões:

  • Bootstrap
  • Chart.js

Instalando o bootstrap

A instalação do bootstrap será feita com os comandos abaixo:

composer require laravel/ui
php artisan ui bootstrap

O segundo comando instala a versão mais recente do bootstrap disponível.

Instalando e configurando o chart.js no bootstrap

O chart.js é uma biblioteca baseada javascript. Biblioteca excelente para a exibição de gráficos.

npm install chart.js

Após a instalação, a configuração é realizada.
Criamos um arquivo de javascript dentro da pasta resources/js com o nome de chart.js. O seguinte conteúdo foi adicionado ao arquivo:

import Chart from  'chart.js/auto';
window.make_chart  =  function  make_chart(id, labels, temperature, humidity) {
    var ctx = document.getElementById(id).getContext('2d');
    var chart =  new  Chart(ctx, {
	    type: 'line',
	    data: {
		    labels: labels,
		    datasets: [
			    {
				    label: 'temperatura',
				    data: temperature,
				    borderColor: 'rgba(255, 0, 0, 1)',
				    backgroundColor: 'rgba(255, 102, 102, 1)',
			    },
			    {
				    label: 'umidade',
				    data: humidity,
				    borderColor: 'rgba(0, 0, 255, 1)',
				    backgroundColor: 'rgba(102, 102, 255, 1)',
			    }
		    ]
	    },
	    options: {}
    });
};

O arquivo acima simplesmente exibi o gráfico com os parâmetros recebidos.

Em seguida, executamos o seguinte comando para compilar e extrair o arquivo js no diretório público do laravel (esse projeto não usa laravel-mix):

npm run build

Configurando o controlador e o view do projeto

No arquivo MessageController.php, criaremos uma função com o nome de index. Essa função será responsável por exibir os dados do banco de dados quando acessado.

public  function  index()
{
    $messages =  Message::orderByDesc('created_at')->simplePaginate(15);

    $data = $messages->items();
    $collectedData =  collect($data);
    $contents = $collectedData->pluck('content');
    $collectedContents =  collect($contents);
    $temperatures = $collectedContents->pluck('temperature');
    $humidities = $collectedContents->pluck('humidity');
    $datetimes = $collectedData->pluck('created_at');
  
    $formattedDatetimes = $datetimes->map(function ($datetime, $key) {
	    return $datetime->format('H:i:s');
    });

    return  response()->view('message.index', [
	    'messages'  => $messages,
	    'keys'  => $formattedDatetimes,
	    'temperatures'  => $temperatures,
	    'humidities'  => $humidities
    ]);
}

O algoritmo acima extrai do banco de dados, os 15 registros mais recentes. A cada página, 15 registros serão exibidos. No fim, esses dados são enviados para o view, responsável pelo interface do usuário.

O view responsável pela exibição será chamado de “index.blade.php” e será colocado dentro do diretório “messages”.

@extends('layouts.app')

@section('content')
<div class="container">
	<h1>Messages</h1>
	<h1>{{ now()->format('Y/m/d H:i:s') }}</h1>
	<div class="row">
		<div class="col-sm-8">
			<canvas id="chart"></canvas>
		</div>
		<div class="col-sm-4"></div>
	</div>

	<script type="module">
		let id = 'chart';
		let labels = @json($keys);
		let temperatures = @json($temperatures);
		let humidities = @json($humidities);
		make_chart(id, labels, temperatures, humidities);
	</script>
	<table class="table">
		<thead>
			<tr>
				<th scope="col">#</th>
				<th scope="col">Temperatura</th>
				<th scope="col">Umidade</th>
				<th scope="col">Data</th>
			</tr>
		</thead>
		<tbody>
@foreach($messages as $message)
				<tr>
					<th scope="row">{{ $message->id }}</th>
					<td>{{ $message->content['temperature'] }}</td>
					<td>{{ $message->content['humidity'] }}</td>
					<td>{{ $message->created_at->format('Y/m/d H:i:s') }}</td>
				</tr>
@endforeach
		</tbody>
	</table>
	{{ $messages->links() }}
</div>
@endsection

O arquivo de exibição acima é bastante simples. Ele extende a classe view parente, importa o chart.js e exibi os 15 registros de cada página.

O resultado será o seguinte:
resultado do projeto laravel e mqtt

Por que usar o protocolo MQTT para comunicação entre dispositivos?

O protocolo MQTT é bastante adequado para a comunicação entre dispositivos devido ao seu baixo consumo de energia e eficiência na transferência de dados mesmo com uma largura de banda pequena.

Por ser um protocolo econômico, o MQTT é um protocolo bastante utilizado na área de IoT, uma vez que grande parte dos dispositivos IoT usam bateria como fonte de energia.

Quais outras possibilidades esse projeto pode providenciar?

Esse projeto é bastante simples. O ESP8266 e o Raspberrypi são dois dispositivos separados fisicamente, porém se encontram na mesma rede interna. Essa prática lembra bastante o Edge Computing, mas tem apenas um servidor que exibi e armazena os dados(raspberrypi).

Usando os dados coletados como referência, algoritmos de decisões podem ser implementados no sistema receptor. Por exemplo, temperaturas muito altas pode representar um perigo para dispositivos. Por isso, ao invés de apenas receber (subscribe) os dados, o raspberrypi também pode exercer o papel de enviar(publish) dados ao ESP8266, pois isso é permitido no protocolo MQTT. Assim, o ESP8266 pode receber esses dados e executar instruções para outros componentes, como atuadores para baixar a temperatura.

Repositório disponível para visualização

Postagens mais vistas

Os 5 principais componentes do computador

Os 5 principais componentes do computador são a unidade de controle, unidade aritmética e lógica, memória, dispositivo de entrada e dispositivo de saída.

Portas TCP e UDP

A porta é um número de 16 bits que é adicionado no final do endereço IP, insinuando qual aplicativo está vinculado e atuando nessa porta.

LAN

Rede local de computadores (LAN) é um conjunto de computadores ou dispositivos conectados uns aos outros de forma isolada em um pequeno local.

Retornar aos projetos