Node-RED

Nodo function de Node-RED: JavaScript para mensajes MQTT

Una guía práctica del nodo function de Node-RED para trabajar con MQTT: remodelar payloads, filtrar ruido, enrutar a varias salidas, conservar estado y gestionar errores, con un flujo que puedes importar y ejecutar contra un broker real.

Editor del nodo function de Node-RED con código JavaScript que transforma mensajes MQTT de sensores
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

El nodo function de Node-RED te permite ejecutar tu propio JavaScript sobre cada mensaje que pasa por el editor, lo que lo convierte en la herramienta más flexible que tienes para remodelar datos MQTT. En esta guía lo usarás para convertir unidades, descartar lecturas erróneas, enrutar mensajes a varias salidas, mantener estado y gestionar errores de forma limpia, todo contra un broker real al que puedes conectarte en segundos.

Qué hace realmente el nodo function de Node-RED

Cada mensaje en Node-RED es un objeto JavaScript simple llamado msg. El nodo function recibe ese objeto, ejecuta el código que has escrito y transmite lo que devuelvas. Si devuelves el mismo objeto, el mensaje continúa. Si devuelves null, el mensaje se detiene. Si devuelves un array, cada elemento va a un puerto de salida distinto. Ese es todo el contrato, y casi todo lo demás es una variación de él.

Dentro de la función tienes acceso a un pequeño conjunto de utilidades:

Objeto Para qué sirve
msg El mensaje entrante, normalmente con payload y topic
node Registro (node.warn, node.error), estado (node.status), envío asíncrono (node.send, node.done)
context, flow, global Guardar valores entre mensajes a nivel de nodo, pestaña o instancia
env Leer variables de entorno y propiedades de subflujos con env.get()
RED.util Utilidades como RED.util.cloneMessage()

El editor también tiene tres pestañas: On Start se ejecuta una vez al desplegar el nodo, On Message se ejecuta con cada mensaje y On Stop se ejecuta cuando el flujo se detiene o se vuelve a desplegar. On Start es el lugar adecuado para inicializar contadores, y On Stop es donde limpias los temporizadores que hayas creado.

Si nunca has montado un flujo MQTT, empieza por nuestro tutorial de Node-RED MQTT, que cubre en detalle el nodo de configuración del broker y los nodos mqtt in y mqtt out.

Prepara una fuente de prueba en el broker público

Necesitas un flujo de mensajes con el que trabajar. El servidor MQTT público gratuito en broker.connectmq.com acepta conexiones anónimas, así que puedes publicar datos de prueba desde una terminal sin registrarte. Elige un prefijo único para que tus topics no choquen con los de nadie más:

# Publica una lectura cada dos segundos (Ctrl+C para detener)
while true; do
  T=$(awk -v r=$RANDOM 'BEGIN{printf "%.1f", 18 + (r % 150) / 10}')
  mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-pub-7f3a \
    -t "connectmq/demo/7f3a/line1/sensor1/telemetry" \
    -m "{\"temp\": $T, \"unit\": \"C\", \"ts\": $(date +%s)}"
  sleep 2
done

En Node-RED, añade un nodo mqtt in, apunta su servidor a broker.connectmq.com en el puerto 1883, asigna al nodo broker un client ID único y no vacío, suscríbete a connectmq/demo/7f3a/# y configura Output como "a parsed JSON object". A partir de ahí cada mensaje llega con msg.payload ya analizado.

Recuerda que el área pública la puede leer cualquiera. Úsala solo para experimentos, nunca para datos reales de planta o de clientes.

Cinco ejemplos del nodo function de Node-RED para MQTT

1. Transformar y enriquecer un payload

Los dispositivos rara vez publican exactamente lo que necesita tu panel o tu base de datos. Esta función convierte grados Celsius a Fahrenheit, añade una etiqueta de ubicación y extrae el nombre del dispositivo del topic:

// On Message
const parts = msg.topic.split("/");      // connectmq/demo/7f3a/line1/sensor1/telemetry
const line = parts[3];
const device = parts[4];

msg.payload = {
    device: device,
    line: line,
    tempC: msg.payload.temp,
    tempF: Math.round((msg.payload.temp * 9 / 5 + 32) * 10) / 10,
    receivedAt: new Date().toISOString()
};
return msg;

Dividir el topic es más barato y fiable que pedir a cada dispositivo que repita su identidad dentro del payload.

2. Filtrar lecturas erróneas

Los sensores fallan. Un nodo function que devuelve null descarta el mensaje en silencio, así que puedes imponer límites de cordura antes de que los datos lleguen al almacenamiento:

const t = msg.payload.temp;
if (typeof t !== "number" || Number.isNaN(t)) {
    node.warn("Non-numeric temp on " + msg.topic);
    return null;
}
if (t < -40 || t > 125) {
    return null;   // fuera del rango del sensor, se descarta
}
return msg;

node.warn escribe en la barra lateral de depuración y en el log de Node-RED, lo que te ayuda a ver con qué frecuencia se filtra sin añadir nodos debug adicionales.

3. Enrutar a varias salidas

Configura Outputs a 2 en los ajustes del nodo y devuelve un array. La posición 0 va al primer puerto y la posición 1 al segundo. Usa null para un puerto que no deba dispararse:

const limit = 30;
if (msg.payload.temp > limit) {
    const alert = {
        topic: msg.topic.replace("/telemetry", "/alert"),
        payload: { level: "high", temp: msg.payload.temp }
    };
    return [msg, alert];   // datos normales + alerta
}
return [msg, null];        // solo datos normales

La segunda salida puede alimentar un nodo mqtt out con el campo topic vacío, de modo que publique en lo que indique msg.topic. Para un enrutamiento sencillo por umbral, el nodo Switch suele ser más claro; recurre a una función cuando la condición implique cálculos o varios campos.

4. Mantener estado entre mensajes

El contexto del nodo se conserva entre mensajes mientras el flujo esté en ejecución. Este ejemplo publica solo cuando el valor cambia más de medio grado, una banda muerta sencilla que reduce mucho el tráfico en señales que varían lentamente:

const key = "last_" + msg.topic;
const last = context.get(key);
const current = msg.payload.temp;

if (last !== undefined && Math.abs(current - last) < 0.5) {
    return null;
}
context.set(key, current);
return msg;

Si necesitas el valor en otra pestaña o tras un reinicio, usa flow, global o un almacén persistente. Nuestra guía sobre el contexto en Node-RED explica los ámbitos y cómo activar el almacenamiento en archivo.

5. Enviar de forma asíncrona

Cuando tu código espera a un temporizador o a una promesa, devolver desde la función es demasiado pronto. Usa node.send() y llama a node.done() al terminar para que los nodos Complete y Catch funcionen correctamente:

// Agrupa lecturas y envía un resumen cada 10 mensajes
let buf = context.get("buf") || [];
buf.push(msg.payload.temp);

if (buf.length >= 10) {
    const avg = buf.reduce((a, b) => a + b, 0) / buf.length;
    context.set("buf", []);
    setTimeout(() => {
        node.send({ topic: "connectmq/demo/7f3a/line1/sensor1/avg", payload: { avg: Number(avg.toFixed(2)) } });
        node.done();
    }, 0);
} else {
    context.set("buf", buf);
    node.done();
}
return null;

Un flujo completo para importar

Copia este JSON, pulsa Ctrl+I en el editor, pégalo y despliega. Después ejecuta el bucle de publicación anterior. Cambia el prefijo 7f3a por tu propia cadena aleatoria en ambos sitios.

[
  {"id":"b1","type":"mqtt-broker","name":"ConnectMQ public","broker":"broker.connectmq.com","port":"1883","clientid":"nr-demo-7f3a","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true},
  {"id":"in1","type":"mqtt in","z":"t1","name":"telemetry","topic":"connectmq/demo/7f3a/+/+/telemetry","qos":"0","datatype":"json","broker":"b1","x":130,"y":100,"wires":[["fn1"]]},
  {"id":"fn1","type":"function","z":"t1","name":"enrich + alert","func":"const p = msg.topic.split('/');\nmsg.payload.device = p[4];\nmsg.payload.tempF = Math.round((msg.payload.temp * 9 / 5 + 32) * 10) / 10;\nif (msg.payload.temp > 30) {\n  return [msg, {topic: msg.topic.replace('/telemetry','/alert'), payload: {level:'high', temp: msg.payload.temp}}];\n}\nreturn [msg, null];","outputs":2,"x":330,"y":100,"wires":[["dbg1"],["out1"]]},
  {"id":"dbg1","type":"debug","z":"t1","name":"data","active":true,"complete":"payload","x":530,"y":80,"wires":[]},
  {"id":"out1","type":"mqtt out","z":"t1","name":"alerts","topic":"","qos":"1","retain":"false","broker":"b1","x":530,"y":130,"wires":[]},
  {"id":"t1","type":"tab","label":"Function demo"}
]

Gestión de errores y depuración

Una excepción lanzada dentro de un nodo function registra un error, pero por defecto el mensaje simplemente desaparece. Dos hábitos hacen visibles los fallos.

Primero, usa node.error(err, msg) con el mensaje como segundo argumento. Esa forma activa cualquier nodo Catch de la misma pestaña, así que puedes enrutar los fallos a un nodo debug dedicado, a un archivo de log o a un topic de alertas.

try {
    msg.payload = JSON.parse(msg.payload.raw);
    return msg;
} catch (err) {
    node.error("Bad raw payload: " + err.message, msg);
    return null;
}

Segundo, usa node.status() para mostrar directamente en el lienzo lo que está haciendo el nodo, por ejemplo el último valor recibido o el número de mensajes descartados:

node.status({ fill: "green", shape: "dot", text: "last " + msg.payload.temp + " C" });

Lista de errores comunes

  1. Modificar un mensaje que también reenvías a otro sitio. Si una salida va a dos cables, Node-RED clona el mensaje por ti, pero los objetos que guardas en el contexto son referencias. Usa RED.util.cloneMessage(msg) antes de almacenarlos.
  2. Olvidar el return. Una función sin sentencia return no envía nada, lo que parece una suscripción rota.
  3. Usar una cadena como si fuera un objeto. Si el nodo mqtt in emite una cadena, msg.payload.temp es undefined. Configura la salida como JSON analizado o añade un nodo JSON; consulta la guía del nodo JSON de Node-RED.
  4. Dejar temporizadores activos. Limpia los intervalos en On Stop, o cada nuevo despliegue añadirá otro.
  5. Hacer trabajo pesado por mensaje. Un nodo function se ejecuta en el mismo bucle de eventos que el resto de Node-RED. Los bucles largos en topics de alta frecuencia ralentizarán todos los flujos. Limita primero el ritmo con el nodo delay de Node-RED.
  6. Poner secretos en el código. Usa env.get("API_KEY") en lugar de pegar tokens en el cuerpo de la función, porque los archivos de flujo a menudo se comparten o se suben a git.

Cuándo no usar un nodo function

El nodo function es potente, pero los flujos construidos con veinte nodos function son difíciles de leer para tus compañeros. Prefiere el nodo Change para asignar y mover propiedades, el nodo Switch para enrutar con condiciones simples y el nodo JSON para analizar. Reserva los nodos function para la lógica que realmente necesita código y dale a cada uno un nombre claro. Si la misma función aparece en varias pestañas, empaquétala como subflujo para corregir los errores en un único sitio.

También puedes cargar módulos npm en un nodo function a través de la pestaña Setup cuando functionExternalModules está activado en settings.js. Es útil para cosas como bibliotecas de formateo de fechas, pero cada módulo que añades es otra dependencia que actualizar y auditar. La documentación oficial del nodo function enumera todo lo disponible en el sandbox.

Ejecuta tus flujos de Node-RED en ConnectMQ

Cuando tu lógica funcione en el broker público, pásala a un broker privado editando un único nodo de configuración. ConnectMQ es un broker MQTT gestionado con TLS en el puerto 8883, MQTT 3.1.1 y 5.0, y un portal con un cliente en el navegador y un explorador de topics en directo, para que veas las alertas que publica tu función. Crea un broker Serverless gratuito en app.connectmq.com sin tarjeta de crédito, copia el usuario, la contraseña y el client ID generados en el nodo broker de Node-RED, marca Use TLS y vuelve a desplegar. Si estás comparando opciones de alojamiento, nuestra visión general de un broker MQTT en la nube explica qué debes buscar.

Preguntas frecuentes

¿Puede un nodo function de Node-RED usar async/await?

Sí. El cuerpo de la función se ejecuta dentro de un envoltorio asíncrono, así que puedes usar await con promesas. Envía los resultados con node.send() y llama a node.done() en lugar de depender del valor de retorno después del await.

¿Cómo envío varios mensajes desde un solo nodo function?

Devuelve un array de mensajes para distintas salidas, o un array dentro de otro array, como return [[m1, m2, m3]], para enviar varios mensajes en secuencia por la primera salida.

¿Por qué mi nodo function no emite nada?

Normalmente la función devuelve null o undefined, o se lanzó una excepción. Revisa la barra lateral de depuración en busca de errores y añade node.warn(msg) al principio para confirmar que llegan mensajes.

¿Es lento el nodo function con datos MQTT de alta frecuencia?

Las transformaciones sencillas son rápidas, pero cada función se ejecuta en el único bucle de eventos de Node.js. Con tasas de mensajes muy altas, mantén el código ligero y filtra o limita el ritmo al principio del flujo.

Artículos relacionados

Cliente OPC UA en Node-RED hacia MQTT con topics ISA-95

Usa node-red-contrib-opcua para suscribirte a variables OPC UA de un PLC o de un servidor de simulación, asignar los node IDs a topics MQTT de estilo ISA-95 y publicar los valores con calidad y marcas de tiempo.

10 min de lectura

Node-RED Dashboard 2.0 con MQTT: indicadores, gráficos y controles

Un tutorial práctico de Node-RED Dashboard 2.0: suscríbete a telemetría MQTT en directo, muéstrala en indicadores y en un gráfico de líneas, y controla un dispositivo con un interruptor que publica comandos y refleja el estado real.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.