ESP32 e Arduino

WS2812B MQTT: Controle uma Fita de LED Endereçável com Comandos JSON

Controle uma fita de LED endereçável WS2812B a partir de um ESP32 com comandos MQTT em JSON para cor, brilho e efeitos e publique o estado retido para manter todos os dashboards sincronizados.

Fita de LED WS2812B colorida controlada por um ESP32 em um projeto de iluminação WS2812B 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 controlador WS2812B via MQTT permite mudar a cor, o brilho e a animação de uma fita de LED endereçável a partir de um celular, de um script, do Node-RED ou de uma central de automação residencial. Neste tutorial, você vai criar um firmware para ESP32 que aceita comandos em JSON, executa efeitos não bloqueantes e publica seu estado como retained message. Tudo é testado no servidor MQTT público broker.connectmq.com com comandos simples de mosquitto_pub.

Formato dos Comandos e Topics

Fitas endereçáveis têm mais estado do que um relé: energia, cor, brilho e efeito. Enviar cada um em um topic separado rapidamente vira bagunça, então este projeto usa um topic de comando em JSON e um topic de estado em JSON. Todos os campos de um comando são opcionais, o que significa que um cliente pode mudar só o brilho sem conhecer a cor atual.

Topic Direção Retido Exemplo
connectmq/demo/<id>/light/set App para o ESP32 Não {"color":[255,80,0],"brightness":120}
connectmq/demo/<id>/light/state ESP32 para os apps Sim {"state":"ON","color":[255,80,0],"brightness":120,"effect":"solid"}
connectmq/demo/<id>/status ESP32 para os apps Sim online / offline

Campos suportados:

Campo Tipo Significado
state "ON" / "OFF" Liga ou desliga a fita sem esquecer a cor
color [r,g,b], de 0 a 255 cada Cor base dos efeitos solid e breathe
brightness 0 a 255 Brilho global, limitado por um teto no firmware
effect solid, rainbow, breathe, chase Modo de animação
speed 1 a 100 Velocidade da animação

Esse formato é próximo do que muitas ferramentas de automação residencial usam para luzes em JSON, então você pode adaptá-lo a uma instalação existente com pouco esforço. Se quiser se aprofundar nos padrões de parsing, o guia de ArduinoJson com MQTT no ESP32 trata com mais detalhe do dimensionamento e da validação de documentos.

Hardware: Ligações e Alimentação do Jeito Certo

A maioria dos projetos de LED que falham, falham na alimentação, e não no código. Um LED WS2812B pode puxar uma corrente considerável em branco total, e alguns metros de fita facilmente ultrapassam o que uma porta USB ou o regulador do ESP32 conseguem fornecer.

Você vai precisar de:

  • Placa de desenvolvimento ESP32.
  • Fita WS2812B (este tutorial considera 60 LEDs; ajuste NUM_LEDS para a sua).
  • Uma fonte de 5 V dimensionada para a sua fita, com folga.
  • Um capacitor de 1000 µF entre o 5 V e o GND da fita, na entrada.
  • Um resistor de 330 Ω em série com a linha de dados.
  • Opcional, mas recomendado: um 74AHCT125 ou conversor de nível semelhante para o sinal de dados.
Fio da fita Ligar em
5V +5 V da fonte
GND GND da fonte e GND do ESP32
DIN GPIO 5 do ESP32 através de 330 Ω (via conversor de nível, se usado)

O ESP32 trabalha com lógica de 3,3 V, enquanto o WS2812B espera um nível alto próximo de 0,7 × VDD. Muitas fitas funcionam sem conversor de nível, especialmente com fios de dados curtos, mas os primeiros pixels piscando ou cores aleatórias são o sintoma clássico de um sinal no limite. Em fitas longas, injete alimentação nas duas pontas para que os LEDs mais distantes não fiquem alaranjados à medida que a tensão cai ao longo do cobre.

O firmware também limita o brilho com MAX_BRIGHTNESS. É um seguro barato: mesmo que alguém envie "brightness":255 com branco, a fita fica dentro do que a sua fonte aguenta.

Bibliotecas

Instale pelo Gerenciador de Bibliotecas do Arduino:

  • PubSubClient, de Nick O'Leary.
  • Adafruit NeoPixel, da Adafruit, que suporta o WS2812B no ESP32.
  • ArduinoJson versão 7, de Benoit Blanchon.

O Firmware ESP32 WS2812B MQTT

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

const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "broker.connectmq.com";
const uint16_t MQTT_PORT  = 1883;
const char* DEVICE_ID     = "leds-92be0f";
const String BASE = String("connectmq/demo/") + DEVICE_ID;

#define LED_PIN 5
#define NUM_LEDS 60
const uint8_t MAX_BRIGHTNESS = 150;   // protege a sua fonte

Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
WiFiClient net;
PubSubClient mqtt(net);

bool powerOn = true;
uint8_t r = 255, g = 120, b = 20;
uint8_t brightness = 100;
uint8_t speed = 50;
String effect = "solid";
bool dirty = true;
unsigned long lastFrame = 0, lastAttempt = 0;
uint16_t frame = 0;

void publishState() {
  JsonDocument doc;
  doc["state"] = powerOn ? "ON" : "OFF";
  JsonArray c = doc["color"].to<JsonArray>();
  c.add(r); c.add(g); c.add(b);
  doc["brightness"] = brightness;
  doc["effect"] = effect;
  doc["speed"] = speed;
  char out[160];
  size_t n = serializeJson(doc, out, sizeof(out));
  String topic = BASE + "/light/state";
  mqtt.publish(topic.c_str(), (const uint8_t*)out, n, true);
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  JsonDocument doc;
  if (deserializeJson(doc, payload, length)) {
    Serial.println("Ignoring invalid JSON");
    return;
  }
  if (doc["state"].is<const char*>()) {
    powerOn = strcmp(doc["state"].as<const char*>(), "OFF") != 0;
  }
  if (doc["color"].is<JsonArray>() && doc["color"].size() == 3) {
    r = constrain(doc["color"][0].as<int>(), 0, 255);
    g = constrain(doc["color"][1].as<int>(), 0, 255);
    b = constrain(doc["color"][2].as<int>(), 0, 255);
  }
  if (doc["brightness"].is<int>()) brightness = constrain(doc["brightness"].as<int>(), 0, 255);
  if (doc["speed"].is<int>()) speed = constrain(doc["speed"].as<int>(), 1, 100);
  if (doc["effect"].is<const char*>()) {
    String e = doc["effect"].as<const char*>();
    if (e == "solid" || e == "rainbow" || e == "breathe" || e == "chase") effect = e;
  }
  dirty = true;
  publishState();
}

void render() {
  uint8_t level = map(brightness, 0, 255, 0, MAX_BRIGHTNESS);
  if (!powerOn) {
    strip.clear();
    strip.show();
    return;
  }
  strip.setBrightness(level);
  if (effect == "solid") {
    strip.fill(strip.Color(r, g, b));
  } else if (effect == "rainbow") {
    strip.rainbow(frame * 256);
  } else if (effect == "breathe") {
    float k = (sin(frame * 0.05f) + 1.0f) / 2.0f;   // 0..1
    strip.fill(strip.Color(r * k, g * k, b * k));
  } else if (effect == "chase") {
    strip.clear();
    for (int i = frame % 3; i < NUM_LEDS; i += 3) strip.setPixelColor(i, strip.Color(r, g, b));
  }
  strip.show();
}

bool connectMqtt() {
  String status = BASE + "/status";
  if (!mqtt.connect(DEVICE_ID, nullptr, nullptr, status.c_str(), 1, true, "offline")) {
    Serial.printf("MQTT failed, state=%d\n", mqtt.state());
    return false;
  }
  mqtt.publish(status.c_str(), "online", true);
  mqtt.subscribe((BASE + "/light/set").c_str(), 1);
  publishState();
  return true;
}

void setup() {
  Serial.begin(115200);
  strip.begin();
  strip.clear();
  strip.show();
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setBufferSize(512);                 // espaço para comandos JSON maiores
  mqtt.setSocketTimeout(5);
}

void loop() {
  // Efeitos animados redesenham com um timer; o solid só quando algo mudou
  unsigned long interval = map(speed, 1, 100, 120, 10);
  bool animated = powerOn && effect != "solid";
  if ((animated && millis() - lastFrame >= interval) || dirty) {
    lastFrame = millis();
    frame++;
    render();
    dirty = false;
  }

  if (WiFi.status() != WL_CONNECTED) return;
  if (!mqtt.connected()) {
    if (millis() - lastAttempt > 5000) { lastAttempt = millis(); connectMqtt(); }
    return;
  }
  mqtt.loop();
}

Escolhas de projeto que vale copiar

Mesclar, não substituir. O callback altera apenas os campos presentes no comando. Enviar {"brightness":40} diminui o brilho da fita sem mexer na cor nem no efeito, que é o que os usuários esperam de uma luz.

Animação não bloqueante. Cada efeito calcula um quadro com base em um contador e retorna. Não há delay() nem um laço for que roda por segundos, então mqtt.loop() continua rodando e os comandos são aplicados imediatamente, mesmo no meio de um arco-íris.

Cores sólidas não são redesenhadas o tempo todo. Em algumas plataformas, strip.show() desativa as interrupções por um instante enquanto envia os dados. Redesenhar uma cor estática centenas de vezes por segundo desperdiça CPU e não traz nenhum ganho.

O estado é publicado após cada comando. Como o topic de estado é retido, um dashboard aberto mais tarde vê a cor atual imediatamente.

Teste com mosquitto_pub

Acompanhe o dispositivo:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-92be0f -v -t "connectmq/demo/leds-92be0f/#"

Envie alguns comandos:

# Laranja quente com brilho médio
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-92be0f -t "connectmq/demo/leds-92be0f/light/set" -m '{"color":[255,90,10],"brightness":120,"effect":"solid"}'

# Arco-íris, mais rápido
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-92be0f -t "connectmq/demo/leds-92be0f/light/set" -m '{"effect":"rainbow","speed":80}'

# Desligado, mantém a cor para a próxima vez
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-92be0f -t "connectmq/demo/leds-92be0f/light/set" -m '{"state":"OFF"}'

Cada comando é seguido por uma mensagem de estado como esta:

{"state":"ON","color":[255,90,10],"brightness":120,"effect":"rainbow","speed":80}

O servidor MQTT público (gratuito) em broker.connectmq.com é ideal para esse tipo de experimento rápido, mas tudo o que está nele pode ser lido por qualquer pessoa, então ele não serve para dados de produção. Use suas próprias credenciais antes de colocar a fita em uma prateleira em casa.

Controle a Fita a Partir de um Script

Testes pela linha de comando são ótimos, mas a maioria dos projetos de LED acaba sendo controlada por algo automático: um agendamento ao pôr do sol, o status de um build, uma campainha. O pequeno script Python abaixo usa o paho-mqtt 2.x para fazer a fita esmaecer até uma cor quente de fim de tarde e depois confirma a mudança lendo o estado retido.

import json
import time
import uuid
import paho.mqtt.client as mqtt

BASE = "connectmq/demo/leds-92be0f"

def on_message(client, userdata, msg):
    print("state:", msg.payload.decode())

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"sched-{uuid.uuid4().hex[:8]}")
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.subscribe(f"{BASE}/light/state", qos=1)
client.loop_start()

for level in range(200, 59, -20):
    client.publish(f"{BASE}/light/set", json.dumps({"state": "ON", "color": [255, 120, 40], "brightness": level}), qos=1)
    time.sleep(1)

time.sleep(2)
client.loop_stop()
client.disconnect()

Como cada comando é uma atualização parcial e o dispositivo sempre responde com o estado completo, o script não precisa saber nada sobre o modo anterior da fita. A mesma ideia funciona no Node-RED com um nó mqtt out e um nó function que monta o objeto JSON, ou em uma central de automação residencial configurada para uma luz JSON. Mantenha os passos do esmaecimento espaçados: um comando por segundo é mais que suficiente, e o lugar certo para animações suaves é o motor de efeitos do ESP32, não a rede.

Checklist de Solução de Problemas

  1. O primeiro LED com cor errada ou piscando. Sinal de dados fraco. Adicione o resistor de 330 Ω, encurte o fio de dados e use um conversor de nível.
  2. Cores trocadas (vermelho aparece verde). A sua fita usa outra ordem de bytes. Tente NEO_RGB em vez de NEO_GRB.
  3. O ESP32 reinicia quando a fita fica branca. Queda na alimentação. Alimente a fita com uma fonte de 5 V própria, compartilhe o terra e reduza MAX_BRIGHTNESS.
  4. O final da fita fica amarelado ou alaranjado. Queda de tensão ao longo da fita. Injete 5 V nas duas pontas ou a cada dois metros, aproximadamente.
  5. Comandos JSON grandes ignorados em silêncio. O PubSubClient descarta mensagens maiores que o seu buffer. O sketch o aumenta para 512 bytes; veja limites de tamanho do buffer do PubSubClient se você enviar dados por pixel.
  6. Os efeitos engasgam quando o MQTT se reconecta. Uma tentativa de conexão bloqueante congela a animação por até o tempo de timeout do socket. Reduza o setSocketTimeout ou mova a rede para uma tarefa própria.

Leve para Produção

Quando a fita sair da bancada para uma sala de estar ou uma vitrine, conecte-a ao seu próprio broker, por exemplo um broker MQTT gerenciado. No portal do ConnectMQ, crie um broker e use o usuário, a senha e o client ID gerados com WiFiClientSecure na porta 8883. Um broker MQTT na nuvem permite que o seu celular, a sua instância do Node-RED e a fita alcancem o mesmo endpoint de qualquer lugar, sem abrir portas no roteador.

O ConnectMQ também oferece um explorador de topics ao vivo, para você ver light/state mudar enquanto testa os efeitos, e o cliente MQTT no navegador é um jeito rápido de enviar comandos JSON sem terminal. Se quiser que a fita mostre status junto com uma telinha, combine-a com um display OLED no ESP32 mostrando mensagens MQTT, ou junte-a ao acionamento por relé para cortar totalmente a energia da fita à noite.

Comece a Usar o ConnectMQ

Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito. Você recebe credenciais seguras na hora e pode controlar sua fita WS2812B de qualquer lugar em poucos minutos.

Perguntas frequentes

Posso controlar pixels WS2812B individuais via MQTT?

Sim, enviando um array de cores por pixel, mas mantenha os payloads pequenos. Em fitas longas, normalmente é melhor enviar nomes de efeitos e parâmetros e deixar o ESP32 renderizar os pixels localmente.

Por que minha fita só pisca quando o Wi-Fi está ativo?

A biblioteca Adafruit NeoPixel no ESP32 usa o periférico RMT, que é robusto contra as interrupções do Wi-Fi, então piscadas durante a atividade do Wi-Fi geralmente indicam ruído na alimentação ou um sinal de dados no limite, e não problemas de temporização.

Quantos LEDs um ESP32 consegue controlar?

O protocolo de dados aguenta centenas de LEDs em um único pino; os limites práticos são a fonte de alimentação, a taxa de quadros e a RAM. Cada LED precisa de três bytes de buffer no modo RGB.

O estado da luz deve ser retido?

Sim. Uma mensagem de estado retida permite que qualquer novo assinante, como um dashboard ou um app de celular, mostre a cor correta logo após se conectar.

Artigos relacionados

Exemplo de MQTT com ESP-IDF: Monte um Cliente esp-mqtt do Zero

Monte um projeto ESP-IDF a partir de uma pasta vazia com o componente esp-mqtt: configuração do Wi-Fi em modo station, o handler de eventos MQTT, publicação e inscrição, propriedades do MQTT 5 e TLS com o bundle de certificados.

8 min de leitura

Usando os Recursos do MQTT 5 do esp-mqtt no ESP32 com ESP-IDF

Um projeto ESP-IDF prático que ativa o MQTT 5.0 no esp-mqtt e usa user properties, expiração de mensagens, topic aliases, expiração de sessão e response topics com um broker MQTT 5 real.

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.