Automação IoT

Controle de Iluminação Inteligente via MQTT: Dimmers, Fitas de LED, Cenas e Agendas

Controle fitas de LED e dimmers via MQTT com um ESP32, use comandos JSON com transições suaves, armazene cenas como mensagens retidas e rode agendas a partir de um pequeno serviço Python.

Fita de LED dimerizável sob uma mesa controlada por um ESP32 em um projeto de iluminação inteligente com 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

O controle de iluminação inteligente via MQTT permite que qualquer app, dashboard ou automação dimerize uma luz, chame uma cena ou execute uma agenda simplesmente publicando uma mensagem. Neste tutorial você vai construir um dimmer PWM com ESP32 para fitas de LED com fades suaves, projetar topics que funcionem para um cômodo ou para um edifício inteiro, armazenar cenas como mensagens retidas e rodar agendas a partir de um pequeno serviço Python conectado a um broker MQTT gerenciado ConnectMQ.

Escolhendo o Hardware de Iluminação para MQTT

Antes de escrever o firmware, decida que tipo de iluminação você vai controlar. Cada opção se encaixa no MQTT de um jeito um pouco diferente.

Hardware Como se conecta ao MQTT Bom para Cuidados
Fitas de LED de baixa tensão com driver PWM no ESP32 Firmware próprio, como neste tutorial Mesas, prateleiras, armários, iluminação de destaque Dimensionamento da fonte e queda de tensão em trechos longos
LEDs endereçáveis (WS2812B e similares) Firmware próprio ou WLED Efeitos e cor por pixel Consumo de corrente, comprimento da linha de dados
Dimmers e relés de rede elétrica com firmware MQTT Tasmota, Shelly ou dispositivos similares Luminárias de teto existentes Apenas hardware certificado, tipo de dimmer correto para a lâmpada
Iluminação DALI ou KNX Bridge do barramento para o MQTT Escritórios e edifícios comerciais O barramento continua sendo o controlador; o MQTT acrescenta visibilidade

Em residências, fitas PWM e relés inteligentes cobrem a maior parte das necessidades. Em edifícios comerciais, a iluminação normalmente já está em DALI ou KNX, e o caminho certo é fazer a bridge desse barramento para o MQTT em vez de substituí-lo. A estrutura de topics abaixo funciona para os quatro casos, e esse é o principal motivo para padronizá-la desde cedo: em um edifício corporativo em São Paulo, os mesmos topics de estado também podem alimentar um gêmeo digital do prédio.

Design de Topics para Luzes

Uma luz precisa de três coisas no MQTT: um topic de comando, um topic de estado e um topic de disponibilidade. Mantê-los separados evita loops de realimentação e permite que os dashboards mostrem o estado real.

<prefix>/<room>/<light>/set          commands in (JSON)
<prefix>/<room>/<light>/state        actual state out (retained)
<prefix>/<room>/<light>/availability "online" / "offline" (retained, last will)
<prefix>/scenes/<scene>              scene definitions (retained)
<prefix>/<room>/scene/set            recall a scene by name

Os comandos usam JSON para que você possa estendê-los depois sem quebrar clientes mais antigos:

{"state": "ON", "brightness": 180, "transition": 2.0}

O brightness vai de 0 a 255 e o transition é o tempo de fade em segundos. Esse formato corresponde ao schema JSON que a integração MQTT do Home Assistant entende para luzes, então o mesmo dimmer pode aparecer no Home Assistant sem alterações no firmware.

Hardware

  • Uma placa de desenvolvimento ESP32.
  • Uma fita de LED monocromática de 12 V ou 24 V e uma fonte compatível.
  • Um MOSFET de canal N com nível lógico (por exemplo, IRLZ44N ou AO3400 em um módulo) por canal, ou uma placa driver de MOSFET pronta.
  • Um resistor de gate de 220 ohms e um pull-down de 10 kOhms por canal.

Ligue o gate do MOSFET ao GPIO 25 através do resistor, o source ao terra (compartilhado com o terra do ESP32) e o drain ao terminal negativo da fita de LED. O terminal positivo da fita vai ao positivo da fonte. Nunca alimente uma fita pelo pino de 5 V do ESP32.

Para dimmers de rede elétrica, use um produto certificado que já fale MQTT, como um dispositivo Tasmota ou Shelly, em vez de construir você mesmo hardware de tensão de rede. O design de topics deste artigo continua valendo; veja MQTT no Tasmota para gravação e configuração.

O Firmware do Dimmer no ESP32

O sketch usa a API LEDC do core Arduino 3.x para ESP32 (ledcAttach e ledcWrite), PubSubClient e ArduinoJson 7. Ele aplica uma curva gama para que a dimerização pareça linear ao olho, faz fades suaves e publica um estado retido após cada mudança.

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <math.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
const int   PORT = 1883;

#define PREFIX "connectmq/demo/light-8pz3/office-201/desk-strip"
const char* T_SET   = PREFIX "/set";
const char* T_STATE = PREFIX "/state";
const char* T_AVAIL = PREFIX "/availability";

const int LED_PIN = 25;
const int PWM_FREQ = 5000;   // Hz, acima da cintilação visível
const int PWM_BITS = 12;     // 0..4095
const int PWM_MAX = (1 << PWM_BITS) - 1;

WiFiClient net;
PubSubClient mqtt(net);

bool on = false;
int brightness = 255;            // último brilho 1..255
float current = 0, target = 0;   // 0..255
float step = 0;
unsigned long lastStep = 0;

void writePwm(float level) {
  float norm = level / 255.0f;
  int duty = (int)(powf(norm, 2.2f) * PWM_MAX + 0.5f);   // correção gama
  ledcWrite(LED_PIN, duty);
}

void publishState() {
  JsonDocument doc;
  doc["state"] = on ? "ON" : "OFF";
  doc["brightness"] = brightness;
  char buf[96];
  size_t n = serializeJson(doc, buf);
  mqtt.publish(T_STATE, (const uint8_t*)buf, n, true);
}

void startFade(float to, float seconds) {
  target = to;
  int steps = max(1, (int)(seconds * 50));     // 50 atualizações por segundo
  step = (target - current) / steps;
  if (seconds <= 0) { current = target; writePwm(current); step = 0; }
}

void onMessage(char* topic, byte* payload, unsigned int len) {
  JsonDocument doc;
  if (deserializeJson(doc, payload, len)) return;

  float transition = doc["transition"] | 0.5f;
  if (doc["brightness"].is<int>()) {
    brightness = constrain(doc["brightness"].as<int>(), 1, 255);
    on = true;
  }
  if (doc["state"].is<const char*>()) {
    on = strcmp(doc["state"].as<const char*>(), "ON") == 0;
  }
  startFade(on ? brightness : 0, transition);
  publishState();
}

void connectMqtt() {
  while (!mqtt.connected()) {
    String id = "light-" + String((uint32_t)ESP.getEfuseMac(), HEX);
    if (mqtt.connect(id.c_str(), T_AVAIL, 1, true, "offline")) {
      mqtt.publish(T_AVAIL, "online", true);
      mqtt.subscribe(T_SET, 1);
      publishState();
    } else {
      delay(2000);
    }
  }
}

void setup() {
  ledcAttach(LED_PIN, PWM_FREQ, PWM_BITS);
  writePwm(0);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(250);
  mqtt.setServer(BROKER, PORT);
  mqtt.setCallback(onMessage);
}

void loop() {
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  if (step != 0 && millis() - lastStep >= 20) {
    lastStep = millis();
    current += step;
    if ((step > 0 && current >= target) || (step < 0 && current <= target)) {
      current = target;
      step = 0;
    }
    writePwm(current);
  }
}

Observe que o fade roda no loop() sem delay(), então o dispositivo continua respondendo ao MQTT enquanto dimeriza. O last will marca a luz como offline se ela perder energia ou Wi-Fi.

Teste o Dimmer

# observa tudo desta demonstração
mosquitto_sub -h broker.connectmq.com -p 1883 -i light-view-8pz3 -t "connectmq/demo/light-8pz3/#" -v

# liga em cerca de 70 por cento com um fade de 2 segundos
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -q 1 \
  -t "connectmq/demo/light-8pz3/office-201/desk-strip/set" \
  -m '{"state":"ON","brightness":180,"transition":2}'

# desliga com um fade lento
mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -q 1 \
  -t "connectmq/demo/light-8pz3/office-201/desk-strip/set" \
  -m '{"state":"OFF","transition":5}'

O servidor MQTT público é aberto a todos, então um desconhecido poderia acionar a sua luz de demonstração. Isso é inofensivo em uma mesa, mas é exatamente por isso que a iluminação real precisa rodar em um broker privado com credenciais.

Cenas como Mensagens Retidas

Uma cena é um conjunto nomeado de estados de luz. Armazenar cada cena como mensagem retida no broker significa que todos os controladores, dashboards ou painéis de parede veem as mesmas definições, e você pode editar uma cena publicando uma nova versão.

mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -r \
  -t "connectmq/demo/light-8pz3/scenes/focus" \
  -m '{"office-201/desk-strip":{"state":"ON","brightness":230},"office-201/ceiling":{"state":"ON","brightness":200}}'

mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 -r \
  -t "connectmq/demo/light-8pz3/scenes/presentation" \
  -m '{"office-201/desk-strip":{"state":"ON","brightness":40},"office-201/ceiling":{"state":"OFF"}}'

Chamada de Cenas e Agendas em Python

Um pequeno serviço assina as definições de cenas e os pedidos de chamada, distribui as chamadas de cena para as luzes individuais e executa uma agenda diária. Ele usa apenas paho-mqtt 2.x e a biblioteca padrão.

import json
import time
import uuid
from datetime import datetime

import paho.mqtt.client as mqtt

BROKER, PORT = "broker.connectmq.com", 1883
PREFIX = "connectmq/demo/light-8pz3"

scenes = {}
SCHEDULE = [            # (HH:MM, dias da semana 0=seg..6=dom, cena)
    ("08:00", range(0, 5), "focus"),
    ("12:30", range(0, 5), "presentation"),
    ("19:00", range(0, 7), "off"),
]
scenes["off"] = {"office-201/desk-strip": {"state": "OFF", "transition": 10}}


def recall(client, name):
    scene = scenes.get(name)
    if not scene:
        print("Unknown scene", name)
        return
    for light, cmd in scene.items():
        cmd = {"transition": 1.5, **cmd}
        client.publish(f"{PREFIX}/{light}/set", json.dumps(cmd), qos=1)
    print("Recalled", name)


def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(f"{PREFIX}/scenes/+", qos=1)
    client.subscribe(f"{PREFIX}/+/scene/set", qos=1)


def on_message(client, userdata, msg):
    parts = msg.topic.split("/")
    if parts[-2] == "scenes":
        if msg.payload:
            scenes[parts[-1]] = json.loads(msg.payload)
    elif parts[-2] == "scene" and parts[-1] == "set":
        recall(client, msg.payload.decode().strip())


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"light-sched-{uuid.uuid4().hex[:6]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, PORT, 60)
client.loop_start()

last_run = None
while True:
    now = datetime.now()
    key = now.strftime("%Y-%m-%d %H:%M")
    if key != last_run:
        for hhmm, days, scene in SCHEDULE:
            if now.strftime("%H:%M") == hhmm and now.weekday() in days:
                recall(client, scene)
        last_run = key
    time.sleep(5)

Chame uma cena manualmente com:

mosquitto_pub -h broker.connectmq.com -p 1883 -i light-cmd-8pz3 \
  -t "connectmq/demo/light-8pz3/office-201/scene/set" -m "focus"

Se você prefere um editor visual para agendas e gatilhos baseados no sol, os mesmos topics funcionam no Node-RED ou nas automações do Home Assistant.

Estendendo para Branco Ajustável e RGB

A mesma estrutura de firmware escala para fitas multicanal. Uma fita de branco ajustável tem um canal quente e um frio; uma fita RGB ou RGBW tem três ou quatro. Associe um canal LEDC a cada pino de cor e estenda o comando JSON com campos opcionais, por exemplo "color_temp": 300 em mireds para branco ajustável, ou "color": {"r": 255, "g": 140, "b": 0} para RGB. Mantenha o brightness como campo separado e escale todos os canais por ele, para que brilho e cor possam mudar de forma independente e uma cena possa definir os dois em uma única mensagem.

Dois pontos práticos evitam problemas depois. Faça a mistura do branco ajustável mantendo constante a soma dos dois canais para um dado brilho, senão a luz fica visivelmente mais forte no meio da faixa. E publique o estado resultante completo, incluindo a cor, no topic de estado após cada comando, porque os dashboards e o Home Assistant dependem dele para posicionar corretamente o seletor de cor.

Checklist de Produção

  1. Migre para um broker privado na porta 8883 com TLS e dê a cada dimmer suas próprias credenciais.
  2. Mantenha os interruptores de parede funcionando localmente; o MQTT deve acrescentar controle, não ser a única forma de acender uma luz.
  3. Combine a iluminação com sensores de ocupação de escritório para que salas vazias se apaguem automaticamente.
  4. Em edifícios com KNX ou DALI, deixe o barramento existente fazer o acionamento e conecte-o ao MQTT via bridge, como em nossa integração KNX MQTT.
  5. Use QoS 1 para comandos e sempre envie valores absolutos, nunca "toggle", para que uma entrega duplicada não cause problemas.
Armadilha Sintoma Correção
Sem correção gama A maior parte da mudança visível acontece no início da faixa Aplique uma curva de potência como no sketch
Fade com delay() A luz ignora comandos durante os fades Avance o fade em etapas no loop()
Estado não retido Dashboards mostram "desconhecido" após reconectar Publique o estado com a flag retain
Comandos no topic de estado As luzes oscilam ou ecoam Separe os topics set e state
Frequência PWM baixa Cintilação visível na câmera Use 1 kHz ou mais

Um broker MQTT gerenciado mantém o controle da iluminação disponível sem um servidor no edifício, e o guia de automação IoT mostra como a iluminação se encaixa com outros sistemas prediais.

Rode Sua Iluminação no ConnectMQ

Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito. Grave seus dimmers com as credenciais emitidas e depois use o cliente MQTT no navegador do portal do ConnectMQ para enviar comandos de teste e chamar cenas enquanto comissiona cada ambiente.

Perguntas frequentes

Por que usar comandos JSON em vez de simples ON e OFF?

O JSON permite que uma única mensagem carregue estado, brilho e tempo de transição, e você pode adicionar campos como temperatura de cor depois sem quebrar os clientes existentes.

Os topics de estado das luzes devem ser retidos?

Sim. Um estado retido permite que qualquer dashboard ou app mostre o estado correto no momento em que assina, sem esperar pela próxima mudança.

Posso controlar luzes de rede elétrica com um ESP32?

Use um dimmer ou módulo de relé de rede elétrica certificado que suporte MQTT, como um dispositivo Tasmota ou Shelly. Construir seu próprio hardware de tensão de rede é arriscado e muitas vezes não atende às normas.

O que acontece com as cenas agendadas se o agendador cair?

As luzes mantêm o estado atual e continuam controláveis pelos interruptores e por comandos MQTT diretos. Rode o agendador como serviço com reinício automático para que eventos perdidos sejam raros.

Artigos relacionados

Guia de Configuração do Zigbee2MQTT: do Coordenador ao Painel

Instale o Zigbee2MQTT com Docker, configure o coordenador e a conexão MQTT, pareie e renomeie dispositivos, controle-os via MQTT e veja seus estados em um painel, testando primeiro no servidor MQTT público (gratuito).

8 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.