Um dashboard OEE no Node-RED é uma das formas mais rápidas de colocar um KPI real de manufatura na frente de uma equipe sem comprar um MES. Neste tutorial você vai calcular disponibilidade, desempenho e qualidade a partir de dois topics MQTT, estado da máquina e contadores de peças, e mostrar o OEE ao vivo em gauges do Dashboard 2.0. Você também vai republicar os KPIs no MQTT para que outras ferramentas possam usá-los e conhecer os casos de borda que deixam errados os números de OEE calculados de forma ingênua.
OEE em um minuto
A Eficiência Global do Equipamento (OEE, Overall Equipment Effectiveness) multiplica três índices, cada um entre 0 e 1:
| Fator | Fórmula | O que ele perde |
|---|---|---|
| Disponibilidade | Tempo de operação / tempo de produção planejado | Quebras, setups, espera por material |
| Desempenho | (Tempo de ciclo ideal x contagem total) / tempo de operação | Ciclos lentos, microparadas |
| Qualidade | Contagem de peças boas / contagem total | Refugo e retrabalho |
| OEE | Disponibilidade x Desempenho x Qualidade | Tudo o que está acima |
A beleza da fórmula é que ela precisa de pouquíssimos dados: quando a máquina estava rodando, quantas peças ela fez e quantas eram boas. Isso se encaixa perfeitamente no MQTT. Para um olhar mais aprofundado nas definições, incluindo paradas planejadas e não planejadas, veja como calcular o OEE.
O modelo de dados MQTT
A máquina, ou um gateway na frente dela, publica dois topics:
| Topic | Payload | Retained | Quando |
|---|---|---|---|
connectmq/demo/a7f3k2/plant1/line1/press1/state |
RUNNING, IDLE ou DOWN |
Sim | A cada mudança |
connectmq/demo/a7f3k2/plant1/line1/press1/count |
{"total": 1234, "good": 1201} |
Não | A cada poucos segundos |
Duas decisões de projeto tornam isso robusto:
- O estado é orientado a eventos e retained. O Node-RED descobre o estado atual logo após se conectar e acumula o tempo de operação a partir dos eventos de mudança.
- Os contadores são acumulados, não por peça. Se uma mensagem se perder, a próxima ainda traz o total correto. O Node-RED calcula as diferenças, e um contador que volta para trás é tratado como um reset do CLP, e não como produção negativa.
O topic segue uma hierarquia no estilo ISA-95 (site, linha, máquina), atrás de um prefixo de demonstração. Substitua a7f3k2 por uma string aleatória sua, porque o broker público pode ser lido por qualquer pessoa.
Simule uma máquina
Este script Python se comporta como uma prensa com ciclo ideal de 2 segundos, paradas ocasionais e cerca de 3 por cento de refugo. Ele publica em broker.connectmq.com, o servidor MQTT público.
# pip install "paho-mqtt>=2.0"
import json
import random
import time
import uuid
import paho.mqtt.client as mqtt
BASE = "connectmq/demo/a7f3k2/plant1/line1/press1"
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"press-sim-{uuid.uuid4().hex[:8]}")
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()
state = None
total = 0
good = 0
next_cycle = time.time()
last_count_pub = 0.0
def set_state(new_state):
global state
if new_state != state:
state = new_state
client.publish(f"{BASE}/state", state, qos=1, retain=True)
print("State:", state)
set_state("RUNNING")
try:
while True:
now = time.time()
if state == "RUNNING":
if random.random() < 0.004:
set_state(random.choice(["IDLE", "DOWN"]))
elif now >= next_cycle:
total += 1
if random.random() > 0.03:
good += 1
# Ciclos reais são um pouco mais lentos que o ideal
next_cycle = now + random.uniform(2.0, 2.6)
elif random.random() < 0.02:
set_state("RUNNING")
next_cycle = time.time() + 2.0
if now - last_count_pub >= 5:
client.publish(f"{BASE}/count",
json.dumps({"total": total, "good": good}), qos=1)
last_count_pub = now
time.sleep(0.2)
except KeyboardInterrupt:
pass
finally:
client.loop_stop()
client.disconnect()
Monte o fluxo do dashboard OEE no Node-RED
Você precisa do Node-RED 4.x com o Dashboard 2.0 (@flowfuse/node-red-dashboard) instalado. Se ainda não o usou, o tutorial do Dashboard 2.0 cobre a instalação e a estrutura de páginas e grupos.
Entradas
- Um nó
mqtt inem.../press1/state, com saída "a UTF-8 string". - Um nó
mqtt inem.../press1/count, com saída "a parsed JSON object". - Um nó inject chamado "tick" que se repete a cada 5 segundos com
msg.topicdefinido comotick. - Um
ui-buttoncom o rótulo "Iniciar novo turno" emsg.topicdefinido comoshift/reset, mais um nó inject com o mesmo topic agendado nos horários de troca de turno (por exemplo 06:00, 14:00 e 22:00).
Use duas assinaturas específicas em vez de um wildcard press1/#. Mais adiante você vai publicar KPIs sob o mesmo caminho da máquina, e um wildcard realimentaria o cálculo com a sua própria saída.
O cálculo do OEE
Ligue as quatro entradas a um único nó function com cinco saídas:
// Nó function "OEE calculator" - defina Outputs como 5
const IDEAL_CYCLE_S = 2.0; // segundos ideais por peça para esta máquina
const now = Date.now();
function newShift() {
return { shiftStart: now, state: "UNKNOWN", since: now, runMs: 0,
lastTotal: null, lastGood: null, total: 0, good: 0 };
}
let s = flow.get("oee") || newShift();
const topic = String(msg.topic || "");
if (topic === "shift/reset") {
const keepState = s.state;
s = newShift();
s.state = keepState; // a máquina continua no mesmo estado
flow.set("oee", s);
return null;
}
if (topic.endsWith("/state")) {
const st = String(msg.payload).trim().toUpperCase();
if (s.state === "RUNNING") s.runMs += now - s.since;
s.state = st;
s.since = now;
flow.set("oee", s);
return null;
}
if (topic.endsWith("/count")) {
const total = Number(msg.payload.total);
const good = Number(msg.payload.good);
if (!Number.isFinite(total) || !Number.isFinite(good)) return null;
if (s.lastTotal !== null) {
// Um contador que volta para trás significa que o CLP o zerou
const dTotal = total >= s.lastTotal ? total - s.lastTotal : total;
const dGood = good >= s.lastGood ? good - s.lastGood : good;
s.total += dTotal;
s.good += Math.min(dGood, dTotal);
}
s.lastTotal = total;
s.lastGood = good;
flow.set("oee", s);
return null;
}
if (topic !== "tick") return null;
// Calcula os KPIs do turno até agora
const runMs = s.runMs + (s.state === "RUNNING" ? now - s.since : 0);
const plannedMs = now - s.shiftStart;
const availability = plannedMs > 0 ? runMs / plannedMs : 0;
const rawPerformance = runMs > 0 ? (IDEAL_CYCLE_S * 1000 * s.total) / runMs : 0;
const performance = Math.min(1, rawPerformance);
const quality = s.total > 0 ? s.good / s.total : 0;
const oee = availability * performance * quality;
if (rawPerformance > 1.05) {
node.warn("Performance above 100%: check IDEAL_CYCLE_S");
}
const pct = v => Math.round(v * 1000) / 10;
node.status({ fill: s.state === "RUNNING" ? "green" : "red", shape: "dot",
text: `${s.state} OEE ${pct(oee)}%` });
return [
{ payload: pct(availability) },
{ payload: pct(performance) },
{ payload: pct(quality) },
{ payload: pct(oee) },
{ topic: "connectmq/demo/a7f3k2/plant1/line1/press1/oee",
payload: { availability: pct(availability), performance: pct(performance),
quality: pct(quality), oee: pct(oee), total: s.total, good: s.good,
state: s.state, shiftStart: new Date(s.shiftStart).toISOString() },
retain: true }
];
Clique em Iniciar novo turno uma vez depois do deploy, para que o turno comece em um momento conhecido.
Widgets
- As saídas 1 a 4 vão para quatro nós
ui-gauge, com faixa de 0 a 100 e unidade%. Um esquema de cores comum para OEE é vermelho abaixo de 60, âmbar de 60 a 85 e verde acima de 85, mas escolha faixas que reflitam a sua própria linha de base. - A saída 5 vai para um nó
mqtt outcom os campos de topic e retain vazios, para que ele usemsg.topicemsg.retainda mensagem. Adicione um nóui-textdepois de um nó change que extraiapayload.statese quiser exibir também o estado da máquina na tela.
Abra /dashboard e observe os números se estabilizarem nos primeiros minutos. No início de um turno, uma única parada faz a disponibilidade oscilar muito; isso é matematicamente correto, e os operadores se acostumam rapidamente.
Armadilhas que distorcem o OEE
- Tempo de ciclo ideal errado. Se o desempenho ficar acima de 100 por cento, o seu ciclo ideal está lento demais. Não se limite a aplicar o teto e seguir em frente; o teto no código esconde um problema de dados.
- Contar paradas planejadas como tempo parado. Intervalos, manutenção planejada e períodos sem pedido normalmente são excluídos do tempo de produção planejado. Adicione um estado
PLANNED_STOPe deixe esse tempo fora deplannedMsse a sua planta usar essa definição. - Perder o estado em reinícios. O contexto de fluxo fica em memória por padrão. Armazene o objeto
oeeem um context store baseado em arquivo para que um reinício do Node-RED no meio do turno não zere os KPIs. - Mensagens fora de ordem. Com QoS 1 e um único publicador, o MQTT preserva a ordem por topic, mas estado e contagem são topics separados. Calcular com um temporizador, como acima, torna irrelevantes as pequenas diferenças de tempo.
- Estouro do contador. Contadores de CLP de 16 bits voltam a zero em 65.535. A lógica de reset trata o estouro como um reset, o que perde no máximo um intervalo de contagens.
Os motivos de parada são o próximo passo natural: adicione um código de motivo ao estado DOWN e acumule os minutos por motivo. O guia de rastreamento de paradas de máquina explica como estruturar esses códigos.
Mantendo o histórico de OEE
Os gauges mostram apenas o turno atual. Para comparar turnos, grave a mensagem retained de KPIs em um banco de séries temporais ao final de cada turno, ou a cada minuto. O tutorial Node-RED InfluxDB mostra como armazenar exatamente esse tipo de JSON com tags como linha e máquina e depois montar gráficos.
Escalando para uma linha ou planta inteira
Um nó function por máquina funciona para um piloto. Para uma linha completa, assine com um wildcard de nível único como plant1/line1/+/state, mantenha um objeto de contexto por máquina indexado pelo nível do topic e publique os KPIs de cada máquina em seu próprio topic. Como o cálculo depende apenas de topics MQTT, não importa se os dados vêm de um gateway de CLP, de um contador ESP32 ou de um dispositivo de borda. A mesma árvore de topics também serve de base para um gêmeo digital da linha, seja em uma planta em Joinville ou em Manaus.
É aí também que o broker faz diferença. Uma planta com dezenas de máquinas, um servidor Node-RED, um historiador e talvez um cliente SCADA conectados ao mesmo tempo precisa de um broker MQTT na nuvem confiável e com autenticação, e não de um serviço público de testes. A ConnectMQ reconhece topics no estilo ISA-95 como plant1/line1/press1/state e agrupa os dispositivos por esses níveis no portal, o que facilita ver quais máquinas estão reportando. Regras de alerta por limite no portal da ConnectMQ também podem sinalizar um valor numérico que cruza um limite escolhido por você, enquanto o Node-RED continua responsável pelo cálculo em si. Se você prefere não operar nada dessa infraestrutura, um broker MQTT gerenciado tira o lado do broker da sua lista de manutenção. Para os fundamentos de broker e Node-RED, comece pelo tutorial Node-RED MQTT.
Coloque seus dados de OEE na ConnectMQ
Cadastre-se em app.connectmq.com no plano Serverless gratuito, sem cartão de crédito. Crie um broker, use o nome de usuário, a senha e o client ID gerados nas suas máquinas e no Node-RED e passe para TLS na porta 8883. Você terá um pipeline de OEE privado funcionando antes da próxima troca de turno.
Perguntas frequentes
De quais dados preciso para calcular o OEE no Node-RED?
Você precisa do estado da máquina ao longo do tempo, de uma contagem total de peças, de uma contagem de peças boas e do tempo de ciclo ideal. Com essas quatro entradas, o Node-RED consegue calcular disponibilidade, desempenho, qualidade e OEE.
Por que as máquinas devem publicar contadores acumulados?
Contadores acumulados tornam o cálculo tolerante a mensagens perdidas ou atrasadas, porque cada mensagem traz o total completo. O Node-RED calcula a diferença e trata os resets.
Com que frequência o dashboard OEE deve ser atualizado?
A cada 5 a 30 segundos é suficiente para os operadores. Calcular com um temporizador, em vez de a cada mensagem, também mantém o dashboard fluido quando muitas máquinas publicam ao mesmo tempo.
Posso mostrar o OEE de várias máquinas em uma única página?
Sim. Use uma assinatura com wildcard, mantenha um objeto de contexto por máquina e adicione um grupo de dashboard por máquina ou uma tabela montada com um nó ui-template.



