Raspberry Pi

Dashboard Quiosque com Raspberry Pi para Dados MQTT ao Vivo via WebSocket

Transforme um Raspberry Pi e qualquer tela HDMI em um dashboard de parede. Uma única página HTML assina o MQTT via WebSocket seguro, e o Chromium abre em tela cheia no modo quiosque a cada boot.

Tela de parede controlada por um dashboard quiosque com Raspberry Pi mostrando blocos ao vivo de temperatura e status de máquinas via MQTT
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

Um dashboard quiosque com Raspberry Pi transforma uma placa barata e qualquer monitor HDMI em um painel de parede que mostra valores de sensores ao vivo, sem teclado, mouse ou login. Neste tutorial você vai escrever um dashboard HTML em um único arquivo que assina o MQTT via WebSocket seguro, servi-lo a partir do Pi e configurar o Chromium para abrir em tela cheia no boot, com o desligamento de tela desativado. O resultado sobrevive a quedas de energia e não exige manutenção além de uma atualização ocasional do sistema, seja no escritório, na recepção ou na linha de produção de uma planta.

Como o quiosque recebe os dados

O navegador no Pi se conecta diretamente ao broker MQTT via WebSocket. Não há servidor de backend, banco de dados ou polling. Quando um sensor publica, o broker empurra a mensagem para todos os assinantes, inclusive a página do quiosque, e o valor na tela muda em instantes.

Esse design depende de o broker oferecer MQTT over WebSocket, porque os navegadores não conseguem abrir sockets TCP brutos. O servidor MQTT público em broker.connectmq.com escuta na porta 8084 com TLS (wss://, caminho /mqtt) e na 8083 sem TLS. Use sempre a porta segura: o Chromium bloqueia conexões WebSocket inseguras a partir de páginas servidas por HTTPS, e a criptografia não tem custo perceptível aqui. O guia de MQTT over WebSocket explica os detalhes do protocolo.

As retained messages fazem o quiosque parecer instantâneo. Se os sensores publicarem seu último estado com a flag retain, o broker entrega esses valores à página no momento em que ela assina, então a tela fica completamente preenchida um segundo depois do boot, em vez de esperar pela próxima leitura.

Peça O que faz Onde roda
Sensores Publicam JSON, de preferência retained Em qualquer lugar com internet
Broker MQTT Roteia mensagens, guarda valores retained Nuvem
dashboard.html Assina via WSS, renderiza os blocos Chromium no Pi
Servidor web local Serve o arquivo HTML Pi, porta 8080
Chromium em quiosque Navegador em tela cheia, sem interface Sessão de desktop do Pi

O que você precisa

  • Raspberry Pi 4 ou 5 (um Pi 3B+ funciona para páginas simples) com Raspberry Pi OS with desktop, Bookworm.
  • Um monitor ou TV via HDMI, ou o display touch oficial.
  • Wi-Fi ou Ethernet com acesso de saída à porta 8084.
  • Algo publicando dados MQTT. Para começar rápido, o publicador de estatísticas do sistema do Raspberry Pi gera valores reais a partir de qualquer Pi.

As imagens Bookworm recentes rodam um desktop Wayland (labwc nas versões atuais, Wayfire nas builds Bookworm mais antigas). Os passos de inicialização automática abaixo cobrem o labwc e indicam a alternativa.

Passo 1: escreva a página do dashboard

Crie uma pasta e a página:

mkdir -p ~/kiosk && nano ~/kiosk/dashboard.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Site Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  :root { color-scheme: dark; }
  body { margin: 0; font-family: system-ui, sans-serif; background: #0f1419; color: #e6e6e6; }
  header { display: flex; justify-content: space-between; padding: 16px 28px; font-size: 22px; }
  #status.ok { color: #4caf50; } #status.bad { color: #f44336; }
  main { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; padding: 0 28px 28px; }
  .tile { background: #1b2229; border-radius: 12px; padding: 24px; }
  .label { font-size: 20px; opacity: .7; }
  .value { font-size: 72px; font-weight: 600; margin-top: 8px; }
  .age { font-size: 16px; opacity: .5; margin-top: 6px; }
  .stale .value { opacity: .35; }
</style>
</head>
<body>
<header><span>Site Dashboard</span><span id="clock"></span><span id="status" class="bad">offline</span></header>
<main id="tiles"></main>
<script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script>
<script>
  const PREFIX = "connectmq/demo/c81f3d";
  const TILES = [
    { topic: PREFIX + "/office/climate",  field: "t",    label: "Office temperature", unit: " °C" },
    { topic: PREFIX + "/office/climate",  field: "rh",   label: "Office humidity",    unit: " %"  },
    { topic: PREFIX + "/pi01/stats",      field: "cpu_temp", label: "Gateway CPU",    unit: " °C" },
    { topic: PREFIX + "/line1/state",     field: null,   label: "Line 1",             unit: ""    }
  ];
  const STALE_MS = 5 * 60 * 1000;

  const tilesEl = document.getElementById("tiles");
  const lastSeen = new Map();
  TILES.forEach((t, i) => {
    const el = document.createElement("div");
    el.className = "tile stale"; el.id = "tile" + i;
    el.innerHTML = '<div class="label"></div><div class="value">--</div><div class="age">waiting</div>';
    el.querySelector(".label").textContent = t.label;
    tilesEl.appendChild(el);
  });

  const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
    clientId: "kiosk-c81f3d-" + Math.random().toString(16).slice(2, 8),
    protocolVersion: 5,
    clean: true,
    reconnectPeriod: 5000,
    connectTimeout: 10000
    // Private broker: add username: "YOUR_USERNAME", password: "YOUR_PASSWORD"
    // and use clientId: "YOUR_CLIENT_ID"
  });

  const statusEl = document.getElementById("status");
  client.on("connect", () => {
    statusEl.textContent = "live"; statusEl.className = "ok";
    const topics = [...new Set(TILES.map(t => t.topic))];
    client.subscribe(topics, { qos: 1 });
  });
  client.on("close", () => { statusEl.textContent = "reconnecting"; statusEl.className = "bad"; });

  client.on("message", (topic, payload) => {
    const text = payload.toString();
    let data = null;
    try { data = JSON.parse(text); } catch (e) { /* plain string payload */ }
    TILES.forEach((t, i) => {
      if (t.topic !== topic) return;
      let v = t.field ? (data ? data[t.field] : undefined) : text;
      if (v === undefined || v === null) return;
      if (typeof v === "number") v = v.toFixed(1);
      const el = document.getElementById("tile" + i);
      el.querySelector(".value").textContent = v + t.unit;
      el.classList.remove("stale");
      lastSeen.set(i, Date.now());
    });
  });

  setInterval(() => {
    document.getElementById("clock").textContent = new Date().toLocaleTimeString();
    TILES.forEach((t, i) => {
      const seen = lastSeen.get(i);
      const el = document.getElementById("tile" + i);
      if (!seen) return;
      const age = Math.round((Date.now() - seen) / 1000);
      el.querySelector(".age").textContent = age < 60 ? "updated " + age + " s ago" : "updated " + Math.round(age / 60) + " min ago";
      el.classList.toggle("stale", Date.now() - seen > STALE_MS);
    });
  }, 1000);
</script>
</body>
</html>

A página faz três coisas que importam em uma tela sem supervisão. Ela mostra um indicador de conexão, para que qualquer pessoa que passe saiba se os números estão ao vivo. Ela deixa em cinza os blocos que não foram atualizados em cinco minutos, o que impede que um sensor morto exiba silenciosamente um valor antigo como se fosse atual. E ela se reconecta sozinha a cada cinco segundos depois de uma queda de rede.

Se preferir não carregar o MQTT.js de uma CDN a cada boot, baixe o arquivo uma vez para ~/kiosk/ e mude o src para mqtt.min.js. O quiosque então só precisa de internet para a conexão com o broker.

Passo 2: sirva a página localmente

O Chromium consegue abrir URLs file://, mas servir por HTTP a partir do Pi evita restrições estranhas do navegador. O servidor embutido do Python é suficiente. Crie um serviço do systemd para que ele suba no boot:

sudo tee /etc/systemd/system/kiosk-web.service >/dev/null <<'EOF'
[Unit]
Description=Kiosk dashboard web server
After=network-online.target
Wants=network-online.target

[Service]
User=pi
WorkingDirectory=/home/pi/kiosk
ExecStart=/usr/bin/python3 -m http.server 8080 --bind 127.0.0.1
Restart=always

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now kiosk-web

Substitua pi pelo seu nome de usuário, se for diferente. Fazer o bind em 127.0.0.1 mantém a página acessível apenas no próprio Pi.

Passo 3: inicie o Chromium no modo quiosque do Raspberry Pi

Primeiro garanta que o Pi inicie direto no desktop com login automático: execute sudo raspi-config, abra System Options, Boot / Auto Login e escolha Desktop Autologin. Aproveite e abra Display Options, Screen Blanking para desativá-lo.

Crie um pequeno script de inicialização:

nano ~/kiosk/start-kiosk.sh
#!/bin/bash
# Aguarda o servidor web local e a rede
for i in $(seq 1 30); do
  curl -s -o /dev/null http://127.0.0.1:8080/dashboard.html && break
  sleep 2
done

BROWSER=$(command -v chromium-browser || command -v chromium)
exec "$BROWSER" \
  --kiosk \
  --noerrdialogs \
  --disable-infobars \
  --disable-session-crashed-bubble \
  --check-for-update-interval=31536000 \
  --ozone-platform=wayland \
  http://127.0.0.1:8080/dashboard.html
chmod +x ~/kiosk/start-kiosk.sh

No labwc, adicione o script ao arquivo de autostart do compositor:

mkdir -p ~/.config/labwc
echo "/home/pi/kiosk/start-kiosk.sh &" >> ~/.config/labwc/autostart

Em uma build Bookworm mais antiga que roda Wayfire, adicione isto a ~/.config/wayfire.ini:

[autostart]
kiosk = /home/pi/kiosk/start-kiosk.sh

Se você voltou o Pi para X11 no raspi-config, remova a flag --ozone-platform=wayland e use ~/.config/autostart/kiosk.desktop com uma linha Exec= apontando para o script. Reinicie e o dashboard deve ocupar a tela inteira.

Blindando um dashboard quiosque com Raspberry Pi sem supervisão

Um quiosque roda por meses sem ninguém olhar para ele, então planeje-se para as falhas sem graça:

  1. Travamentos. A flag --disable-session-crashed-bubble impede a barra de "restaurar páginas" depois de uma queda de energia. Para mais segurança, coloque o exec dentro de um loop para que o Chromium reinicie se for encerrado.
  2. Crescimento de memória. Abas de navegador abertas por muito tempo podem crescer. Um reboot noturno via sudo crontab -e com 0 4 * * * /sbin/reboot é uma solução grosseira, mas confiável.
  3. Desgaste do cartão SD. O Chromium grava um perfil o tempo todo. Considere ativar o overlay file system no raspi-config quando a configuração estiver finalizada, o que deixa o sistema de arquivos raiz somente leitura.
  4. Marcação de tela (burn-in). Layouts estáticos em telas OLED ou de plasma podem deixar imagens fantasmas. Use fundos escuros e desligue o display à noite com wlr-randr --output HDMI-A-1 --off a partir de um script agendado.
  5. Credenciais na tela. Tudo o que está no código-fonte da página fica visível para quem tem acesso físico. Use um cliente dedicado, com credenciais próprias, para o quiosque.

Produção: um broker privado para o quiosque

O servidor público é ideal durante a montagem, mas qualquer pessoa pode ler e escrever nos topics dele, então um painel de parede de verdade deve assinar o seu próprio broker. Um broker MQTT gerenciado como o ConnectMQ oferece um broker privado com WebSocket e WebSocket seguro ativados de fábrica. No portal você cria um broker e recebe usuário, senha e client ID; coloque-os nas opções do mqtt.connect e use exatamente o client ID emitido, porque o ConnectMQ vincula os client IDs às credenciais. Como a página usa uma única conexão, ela cabe com folga no plano Serverless gratuito ao lado dos seus sensores.

Antes de ligar tudo ao quiosque, o cliente MQTT no navegador e o explorador de topics ao vivo do portal ConnectMQ são úteis para verificar se os sensores publicam os topics e campos JSON que os seus blocos esperam.

Solução de problemas

Sintoma Causa Solução
O status fica em "offline" Porta 8084 bloqueada ou caminho errado Use wss://...:8084/mqtt e verifique o firewall
Os blocos mostram -- para sempre Topic ou campo não correspondem Assine com mosquitto_sub -v e compare
Os valores só aparecem depois de minutos Os sensores não usam retain Publique o último valor com a flag retain
Tela preta depois do boot Arquivo de autostart não lido Verifique qual compositor roda com echo $XDG_SESSION_DESKTOP
A tela desliga após 10 minutos Screen blanking ativado Desative no raspi-config, Display Options

Comece com o ConnectMQ

Dê ao seu quiosque um feed privado e protegido por TLS: crie um broker em app.connectmq.com no plano Serverless gratuito, sem cartão de crédito, e cole as credenciais no dashboard.html. Para montar uma versão touchscreen que também envia comandos, continue com o tutorial de display MQTT com Raspberry Pi, ou volte ao básico com o tutorial MQTT com Raspberry Pi.

Perguntas frequentes

Por que meu quiosque precisa de MQTT over WebSocket em vez da porta 1883?

Os navegadores só abrem conexões HTTP e WebSocket, não sockets TCP brutos, então uma página web não consegue usar a porta 1883 diretamente. O WebSocket seguro na porta 8084 é a forma padrão de assinar a partir do navegador.

Posso usar o Grafana em vez de uma página HTML própria?

Sim, abra a URL de um dashboard do Grafana no modo quiosque da mesma forma. Uma página própria é mais leve e atualiza instantaneamente, enquanto o Grafana é melhor quando você precisa de histórico e gráficos.

Como saio do modo quiosque do Chromium?

Conecte um teclado e pressione Alt+F4, ou acesse o Pi por SSH e execute pkill chromium. Remova a linha de autostart se quiser o desktop normal de volta.

O quiosque continua funcionando se a internet cair?

A página continua na tela, se marca como reconectando e deixa os blocos em cinza conforme ficam desatualizados. Quando a conexão volta, o MQTT.js se reconecta e os valores retained atualizam a tela.

Artigos relacionados

Registro de Acessos Raspberry Pi RFID MQTT com o Leitor RC522

Ligue um leitor RFID RC522 a um Raspberry Pi, leia tags em Python, confira-as em uma lista de permissões local e publique cada tentativa de acesso como um evento MQTT que loggers, dashboards e ferramentas de alerta podem consumir.

8 min de leitura

Confiabilidade do Raspberry Pi: Rodando um Pi 24/7 como Gateway IoT

Um guia prático para manter um gateway MQTT com Raspberry Pi rodando por meses: desgaste do armazenamento, sistemas de arquivos somente leitura, alimentação, refrigeração, watchdogs de hardware e software e monitoramento remoto de saúde.

9 min de leitura

Pico W vs Pico 2 W para MQTT: Desempenho, Memória e TLS do RP2040 vs RP2350

O Pico 2 W mantém o formato e o chip Wi-Fi do Pico W, mas troca o RP2040 pelo RP2350, mais rápido. Esta comparação mostra o que isso significa para dispositivos MQTT: CPU, memória, TLS, recursos de segurança, suporte de software e qual placa comprar para cada projeto.

9 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.