Prečo MQTT a nie HTTP polling?

HTTP polling — každých 5 sekúnd pýtať server „sú nové dáta?" — plytváva batériu ESP32 a zaťažuje server. MQTT je lightweight publish/subscribe protokol navrhnutý pre IoT: ESP32 publikuje správu, broker ju okamžite doručí všetkým odberateľom. Latencia pod 100 ms, spotreba energie zlomok oproti HTTP.

VlastnosťHTTP pollingMQTT
Latenciapodľa intervalu (5–60 s)< 100 ms
Spotreba ESP32vysoká (stále aktívny WiFi)nízka (publish + sleep)
Škálovateľnosť1 server ← N zariadeníbroker distribuuje N→M
ProtokolTCP/HTTPTCP/MQTT (port 1883/8883)

Architektúra systému

Stack pozostáva zo štyroch vrstiev: ESP32 senzor publikuje na MQTT topic, Mosquitto broker distribuuje správy, Laravel subscriber prijíma dáta a ukladá ich do databázy, Reverb WebSocket pushuje živé dáta do Blade dashboardu.

ESP32 (DHT22)
  └─ publish → topic: home/livingroom/temp
       │
  Mosquitto broker (VPS port 1883)
       │
  Laravel artisan command (php-mqtt/client)
       │
  MySQL + Reverb broadcast
       │
  Blade dashboard (EventSource / Echo)

1. Mosquitto broker — inštalácia na VPS

sudo apt install -y mosquitto mosquitto-clients

# /etc/mosquitto/conf.d/default.conf
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd

# Vytvor používateľa
sudo mosquitto_passwd -c /etc/mosquitto/passwd gear_iot
sudo systemctl restart mosquitto

# Test
mosquitto_pub -h localhost -u gear_iot -P tajneheslo -t test/hello -m "OK"
mosquitto_sub -h localhost -u gear_iot -P tajneheslo -t test/hello

2. ESP32 — publikovanie dát (PubSubClient)

Knižnica PubSubClient od Nicka O'Learyho je štandard pre MQTT na Arduino/ESP32. Inštalácia cez Arduino IDE Library Manager alebo PlatformIO.

#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>

const char* ssid     = "WIFI_SSID";
const char* password = "WIFI_PASS";
const char* mqtt_server = "vps.example.com";
const char* mqtt_user   = "gear_iot";
const char* mqtt_pass   = "tajneheslo";

DHT dht(4, DHT22);
WiFiClient espClient;
PubSubClient client(espClient);

void reconnect() {
    while (!client.connected()) {
        if (client.connect("esp32-livingroom", mqtt_user, mqtt_pass)) {
            Serial.println("MQTT connected");
        } else {
            delay(5000);
        }
    }
}

void setup() {
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) delay(500);
    client.setServer(mqtt_server, 1883);
    dht.begin();
}

void loop() {
    if (!client.connected()) reconnect();
    client.loop();

    float temp = dht.readTemperature();
    float hum  = dht.readHumidity();

    if (!isnan(temp) && !isnan(hum)) {
        String payload = "{\"temp\":" + String(temp, 1)
                       + ",\"hum\":"  + String(hum, 1)
                       + ",\"ts\":"   + String(millis()) + "}";
        client.publish("home/livingroom/sensor", payload.c_str());
    }
    delay(30000); // každých 30 sekúnd
}

3. Laravel — MQTT subscriber

Nainštaluj knižnicu php-mqtt/laravel-client, ktorá obaľuje php-mqtt/client a integruje sa s Laravel service containerom.

composer require php-mqtt/laravel-client

# config/mqtt-client.php — hlavné nastavenia
'default_connection' => 'default',
'connections' => [
    'default' => [
        'host'     => env('MQTT_HOST', 'vps.example.com'),
        'port'     => env('MQTT_PORT', 1883),
        'username' => env('MQTT_USER'),
        'password' => env('MQTT_PASS'),
        'client_id' => 'laravel-subscriber',
    ],
],
// app/Console/Commands/MqttSubscribe.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use PhpMqtt\Client\Facades\MQTT;
use App\Models\SensorReading;
use App\Events\SensorUpdated;

class MqttSubscribe extends Command
{
    protected $signature   = 'mqtt:subscribe';
    protected $description = 'Počúva MQTT správy zo senzorov';

    public function handle(): void
    {
        MQTT::subscribe('home/+/sensor', function (string $topic, string $message) {
            $data = json_decode($message, true);
            if (!$data) return;

            // Extrahuj miestnosť z topicu: home/livingroom/sensor
            $room = explode('/', $topic)[1];

            $reading = SensorReading::create([
                'room'        => $room,
                'temperature' => $data['temp'],
                'humidity'    => $data['hum'],
            ]);

            broadcast(new SensorUpdated($reading))->toOthers();
        });

        MQTT::loop(); // blokujúca slučka
    }
}

Spustenie cez Supervisor

[program:laravel-mqtt]
command=php /var/www/gear/artisan mqtt:subscribe
autostart=true
autorestart=true
stderr_logfile=/var/log/mqtt-subscribe.err.log
stdout_logfile=/var/log/mqtt-subscribe.out.log

4. Reverb WebSocket + broadcast event

# Inštalácia Reverb
composer require laravel/reverb
php artisan reverb:install

# .env
BROADCAST_DRIVER=reverb
REVERB_APP_ID=gear-iot
REVERB_APP_KEY=tajny-kluc
REVERB_APP_SECRET=tajny-secret
REVERB_HOST=0.0.0.0
REVERB_PORT=8080
// app/Events/SensorUpdated.php
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class SensorUpdated implements ShouldBroadcast
{
    public function __construct(public SensorReading $reading) {}

    public function broadcastOn(): Channel
    {
        return new Channel('sensors.' . $this->reading->room);
    }

    public function broadcastWith(): array
    {
        return [
            'room'        => $this->reading->room,
            'temperature' => $this->reading->temperature,
            'humidity'    => $this->reading->humidity,
            'updated_at'  => $this->reading->created_at->toIso8601String(),
        ];
    }
}

5. Blade dashboard — live aktualizácie

<!-- resources/views/dashboard.blade.php -->
<div id="temp-display">--°C</div>
<div id="hum-display">--%</div>

<script src="https://js.pusher.com/8.2.0/pusher.min.js"></script>
<script>
const pusher = new Pusher('{{ config("broadcasting.connections.reverb.key") }}', {
    wsHost:      '{{ config("broadcasting.connections.reverb.options.host") }}',
    wsPort:      {{ config("broadcasting.connections.reverb.options.port") }},
    cluster:     '',
    forceTLS:    false,
    enabledTransports: ['ws'],
});

const channel = pusher.subscribe('sensors.livingroom');
channel.bind('App\\Events\\SensorUpdated', function(data) {
    document.getElementById('temp-display').textContent = data.temperature + '°C';
    document.getElementById('hum-display').textContent  = data.humidity + '%';
});
</script>

6. Bezpečnosť — TLS pre MQTT

Na produkciu nikdy nepoužívaj MQTT na porte 1883 bez šifrovania. Mosquitto podporuje TLS cez Let's Encrypt certifikáty na porte 8883.

# /etc/mosquitto/conf.d/tls.conf
listener 8883
cafile   /etc/letsencrypt/live/vps.example.com/chain.pem
certfile /etc/letsencrypt/live/vps.example.com/cert.pem
keyfile  /etc/letsencrypt/live/vps.example.com/privkey.pem
tls_version tlsv1.2

V ESP32 kóde prepni na port 8883 a použi WiFiClientSecure s CA certifikátom. V Laravel config nastav 'port' => 8883, 'tls' => ['enabled' => true].

Zhrnutie