DOCUMENTACIÓN CON IA
¿Qué desea saber?
Conectar a Tulip
Tulip es una plataforma sin código de MES / operaciones conectadas en línea frontal. La cámara OV20i se conecta a Tulip a través de Node-RED ejecutándose en la cámara — el mismo editor de flujos que utiliza para integraciones de PLC, MQTT y otras E/S. Tulip puede:
- Disparar una inspección desde un botón del operador o un evento de aplicación.
- Recibir los resultados de aprobado/rechazado por ROI y globales, además de los metadatos de captura.
- Cambiar la receta activa cuando el operador escanea un código de barras de pieza o selecciona un producto diferente.
- Mostrar la imagen en vivo (con superposiciones de cuadros delimitadores) dentro de una aplicación de Tulip para que el operador vea lo que la AI vio.
Esta guía recorre cada uno de esos cuatro puntos de integración de extremo a extremo.
Arquitectura
┌────────────────────────────┐ ┌──────────────────────────────┐
│ TULIP │ │ OV20i │
│ │ │ │
│ ┌──────────────────┐ │ HTTP/MQTT │ ┌──────────────────────┐ │
│ │ Operator Button │──────┼───trigger─────┼──▶│ Node-RED HTTP IN │──▶│
│ │ in Tulip App │ │ │ │ /trigger │ │
│ └──────────────────┘ │ │ └──────────────────────┘ │
│ │ │ │ │
│ ┌──────────────────┐ │ │ ▼ │
│ │ Tulip Connector │◀─────┼─── results ───┼─── All Block Outputs │
│ │ Function (HTTP) │ │ │ (image_url, pass/fail, │
│ └──────────────────┘ │ │ per-ROI classes) │
│ │ │ │ │
│ ┌──────────────────┐ │ │ ▼ │
│ │ Image Widget │◀─────┼── live-feed ──┼─── http://CAMERA_IP/live-feed│
│ │ (iframe URL) │ │ (HMI w/ ROI │ (shows bounding boxes │
│ └──────────────────┘ │ overlays) │ on the captured image) │
│ │ │ │
│ ┌──────────────────┐ │ │ ┌──────────────────────┐ │
│ │ Recipe Selector │──────┼─── HTTP ──────┼──▶│ /pipeline/activate │ │
│ │ (barcode / btn) │ │ │ │ {id: <recipe_id>} │ │
│ └──────────────────┘ │ │ └──────────────────────┘ │
└────────────────────────────┘ └──────────────────────────────┘
El puente en el medio es siempre Node-RED ejecutándose dentro del Bloque IO de la cámara — que es donde se construyen las cuatro integraciones.

Codificar de forma fija la URL del Conector de Tulip en el flujo de Node-RED ata el flujo a una sola implementación. Defina una variable de entorno TULIP_ENDPOINT en System Settings → Environment Variables, luego léala desde sus nodos HTTP Request con ${TULIP_ENDPOINT}. Esto le permite exportar el mismo flujo e importarlo nuevamente en cada cámara de la línea. Consulte Environment Variables para ver el patrón completo.
Elija su transporte: HTTP o MQTT
| Opción | Cuándo usar | Notas |
|---|---|---|
| HTTP (recomendado para la mayoría de las implementaciones de Tulip) | Las Connector Functions de Tulip llaman de forma nativa a endpoints HTTP. La manera más sencilla de activar desde un botón de widget de Tulip. | Funciona sin necesidad de infraestructura adicional. Tulip → cámara y cámara → Tulip ambos usan un simple POST. |
| MQTT | Su planta ya ejecuta un broker MQTT (Sparkplug B, broker IT/OT, etc.) y el Tulip Edge Connector está configurado para comunicarse con él. | Menor latencia para señalización de alto volumen. Requiere un broker. |
| OPC UA | Está ejecutando una instancia de Tulip Edge MC con el OPC UA Connector. | El OV20i viene con bloques de Node-RED de OPC UA preinstalados. |
El resto de esta guía utiliza HTTP en los ejemplos — es lo que la mayoría de las implementaciones de Tulip usan por defecto. Los mismos patrones aplican a MQTT (reemplace los nodos HTTP IN / HTTP Request con nodos MQTT in / MQTT out).
1. Activar la cámara desde una aplicación Tulip
Un operador de Tulip presiona un botón → la Connector Function de Tulip llama a un endpoint HTTP que el Node-RED de la cámara está escuchando → Node-RED activa una captura.
Lado de Node-RED: recibir el disparador
En el Bloque IO de la cámara (Modo Avanzado → Node-RED), construya:
[ HTTP IN: POST /trigger ]──▶[ Trigger node ]──▶[ HTTP Response: 200 ok ]
- Arrastre un nodo http in desde la categoría network.
- Method:
POST - URL:
/trigger
- Method:
- Conéctelo al nodo Trigger específico de OV (bajo block logic en la paleta de OV). El nodo Trigger dispara el pipeline de captura de la cámara.
- Agregue un nodo http response para que el Connector de Tulip reciba un
200 OKde inmediato (no haga que Tulip espere la inferencia de AI — deje que el resultado regrese de forma asíncrona, vea la Sección 2). - Haga clic en Deploy.
El endpoint ahora está activo en http://<CAMERA_IP>/node-red/trigger (la raíz HTTP de Node-RED está montada bajo /node-red/).
Lado de Tulip: llamar al disparador desde un botón
En su cuenta de Tulip:
- Vaya a Apps → Connectors y cree un nuevo HTTP Connector si no tiene uno.
- Configure el Host con la IP de su cámara y el Port a
80(o443si ha habilitado HTTPS). - Cree una nueva Connector Function llamada
Trigger Camera:- Method:
POST - Endpoint:
/node-red/trigger - No se necesita cuerpo ni parámetros de entrada para un disparador básico — la cámara utiliza la receta activa.
- Method:
- Guarde y pruebe la Connector Function. Debería ver una respuesta 200 y la cámara debería capturar una imagen.
- En su aplicación Tulip, agregue un widget Button. En su acción, elija Run Connector Function → Trigger Camera.
Cuando el operador toca el botón, la cámara se dispara.
Si desea enviar un número de serie o un ID de pieza con cada disparador, cambie la Connector Function de Tulip para aceptar entradas (por ejemplo, serial_number) y colóquelas en el cuerpo de la solicitud. En el lado de la cámara, su nodo HTTP IN las recibe como msg.payload y puede almacenarlas en una variable de flujo para que el flujo de envío de resultados (Sección 2) pueda adjuntarlas al reporte de inspección.
2. Enviar resultados de vuelta a Tulip
Después de que la cámara captura, el nodo All Block Outputs se activa con el resultado completo de la inspección. El patrón:
[ All Block Outputs ]──▶[ Function: format for Tulip ]──▶[ HTTP Request: POST to Tulip ]
Construir el flujo
- Conecte un nodo function después del nodo All Block Outputs.
- La función consolida los resultados por ROI en un único aprobado/rechazado global y recorta el payload a lo que Tulip realmente necesita:
// Read the camera's full inspection payload
const p = msg.payload || {};
// Consolidate per-ROI classifier predictions into a global pass/fail
const preds = p.classification?.predictions || [];
const allPass = preds.length > 0 && preds.every(r => r.predicted_class === 'pass');
// Optional: count segmentation defects (e.g., for thresholded fail)
const defectPixels = (p.segmentation?.blobs || [])
.reduce((s, b) => s + (b.pixel_count || 0), 0);
// Build a flat object for Tulip (Tulip Connector Functions like flat fields)
msg.payload = {
result: allPass ? "pass" : "fail",
inspection_id: p.inspection_id || null,
timestamp: new Date().toISOString(),
image_url: p.image_url || null, // see Section 4 for displaying this in Tulip
defect_pixel_count: defectPixels,
per_roi: preds.map(r => ({
roi: r.roi_name,
class: r.predicted_class,
confidence: r.confidence,
})),
// If you stashed a serial number from the trigger (see Section 1), add it:
// serial_number: flow.get('lastSerial') || null,
};
// Set headers for the outbound HTTP request
msg.headers = { 'Content-Type': 'application/json' };
msg.method = 'POST';
return msg;
- Conéctelo a un nodo http request:
- Method:
POST(o usemsg.methodde la función anterior) - URL:
${TULIP_ENDPOINT}(leído desde la variable de entorno) o la URL del Tulip Connector codificada directamente - Return:
a UTF-8 string(o JSON si su endpoint de Tulip devuelve JSON)
- Method:
- Conecte un nodo debug después del http request para que pueda inspeccionar la respuesta de Tulip durante las pruebas.
- Haga clic en Deploy.
Lado de Tulip: recibir los resultados
En Tulip:
- Cree una HTTP Connector Function en la dirección de recepción — un endpoint de webhook, o use la Tables API de Tulip para escribir los resultados en una Tulip Table.
- Configure la lógica de su app de Tulip para reaccionar al payload entrante (por ejemplo, actualizar un indicador de estado, registrar en una Table, incrementar un contador).
Para Tulip Tables específicamente, el patrón es hacer POST a https://<your-tulip-instance>.tulip.co/api/v3/tables/<table_id>/records con los campos result, serial_number, timestamp. Use un token de API de Tulip en el header Authorization — almacene el token como una credencial en el HTTP Connector, no en el flujo de Node-RED.
3. Cambiar recetas desde Tulip
Los operadores a menudo necesitan cambiar a una receta diferente cuando cambia el tipo de producto (escaneo de código de barras, lista desplegable, dispatch). La cámara expone un endpoint HTTP integrado que activa una receta por ID.
Encontrar el ID de la receta
- Abra la receta en el editor de recetas.
- Mire la barra de direcciones de su navegador:
/recipes/15/editor→ el ID es15.
Construir el flujo de cambio de receta
[ HTTP IN: POST /change-recipe ]──▶[ Function: format request ]──▶[ HTTP Request: POST localhost:5001/pipeline/activate ]──▶[ HTTP Response ]
- Nodo HTTP IN:
POST /change-recipe. Tulip lo llamará con un cuerpo como{ "id": 15 }. - Nodo Function — pasa el ID de la receta a la API interna de la cámara:
const recipeId = msg.payload?.id;
if (!recipeId) {
msg.statusCode = 400;
msg.payload = { error: "missing 'id' field" };
return msg;
}
msg.headers = { 'Content-Type': 'application/json' };
msg.payload = JSON.stringify({ id: recipeId });
msg.method = 'POST';
return msg;
- Nodo HTTP Request:
- Method:
POST - URL:
localhost:5001/pipeline/activate - Return:
a parsed JSON object
- Method:
- Nodo HTTP Response para que la Connector Function de Tulip reciba un código de estado.
Dentro de Node-RED en la cámara, el endpoint de cambio de receta es localhost:5001/pipeline/activate — esa es la API administrativa interna de la cámara. Usted está redirigiendo la solicitud de Tulip a través de Node-RED hacia la API local. No intente llamar al puerto 5001 directamente desde fuera de la cámara; sólo está expuesto a los flujos internos de la cámara.
Para firmware más antiguo (anterior a v18.92), la URL era http://[CAMERA_IP]/edge/pipeline/activate. Si está en firmware más antiguo y localhost:5001 no responde, recurra a la URL de v17/v18. Actualice el firmware de la cámara a v18.92+ para usar la ruta moderna.
Lado de Tulip: llamar al cambio de receta
- Agregue una Connector Function
Switch Recipeen el mismo HTTP Connector. - Method:
POST, Endpoint:/node-red/change-recipe. - Agregue un parámetro de entrada
recipe_id(número). - Establezca el cuerpo de la solicitud en
{ "id": $recipe_id }. - En su aplicación de Tulip, conecte un disparador Barcode Scanner o un widget Dropdown para llamar a esta función con el ID de receta correspondiente.
El operador escanea un código de barras → Tulip busca el ID de receta para esa pieza → la Connector Function llama a la cámara → la cámara cambia de receta en menos de un segundo.
4. Mostrar la imagen en vivo (con cuadros delimitadores) en Tulip
El Image Widget de Tulip puede mostrar cualquier URL que devuelva una imagen. El OV20i tiene una URL de HMI integrada que devuelve la captura más reciente con cuadros delimitadores y superposiciones de ROI dibujados encima:
http://<CAMERA_IP>/live-feed

Para incrustarlo en una aplicación de Tulip:
- Agregue un Image Widget a su paso de Tulip.
- Establezca la Image URL en una variable de Tulip que se llene a partir del resultado de la inspección, o en una URL estática si cada cámara tiene su propio paso de panel.
- El patrón más simple: codificar de forma fija
http://<CAMERA_IP>/live-feedy dejar que se actualice automáticamente en cada captura (el Image Widget de Tulip se actualiza cuando su URL o un parámetro de consulta cambia — agregue?t={{timestamp}}para forzar la actualización).
Si desea la captura sin procesar (sin cuadros delimitadores), use image_url desde msg.payload en su lugar — esa es una URL por captura que apunta a la imagen guardada en la Biblioteca de la cámara.
Si tiene varias cámaras en la misma línea, cada una con su propia IP, almacene la URL de la cámara en una variable de Tulip por paso de aplicación y deje que el operador elija qué estación visualizar. El Image Widget cambia instantáneamente cuando la variable cambia.
Flujo de referencia (JSON de Node-RED importable)
El siguiente flujo combina las cuatro integraciones: HTTP IN de activación, HTTP Request de envío de resultados, conmutador de recetas y un nodo de depuración. Copie el JSON, abra Node-RED → menú de hamburguesa → Import, y péguelo.
[
{
"id": "tulip_trigger_in",
"type": "http in",
"name": "POST /trigger",
"url": "/trigger",
"method": "post",
"x": 160, "y": 100, "wires": [["tulip_trigger_fire"]]
},
{
"id": "tulip_trigger_fire",
"type": "trigger-camera",
"name": "Trigger camera",
"x": 380, "y": 100, "wires": [["tulip_trigger_resp"]]
},
{
"id": "tulip_trigger_resp",
"type": "http response",
"name": "200 ok",
"statusCode": "200",
"x": 580, "y": 100, "wires": []
},
{
"id": "tulip_results_format",
"type": "function",
"name": "Format for Tulip",
"func": "const p = msg.payload || {};\nconst preds = p.classification?.predictions || [];\nconst allPass = preds.length > 0 && preds.every(r => r.predicted_class === 'pass');\nmsg.payload = {\n result: allPass ? 'pass' : 'fail',\n timestamp: new Date().toISOString(),\n image_url: p.image_url || null,\n per_roi: preds.map(r => ({roi: r.roi_name, class: r.predicted_class, confidence: r.confidence}))\n};\nmsg.headers = {'Content-Type':'application/json'};\nmsg.method = 'POST';\nreturn msg;",
"x": 380, "y": 200, "wires": [["tulip_results_send"]]
},
{
"id": "tulip_results_send",
"type": "http request",
"name": "POST to Tulip",
"method": "POST",
"url": "${TULIP_ENDPOINT}",
"ret": "obj",
"x": 600, "y": 200, "wires": [["tulip_results_debug"]]
},
{
"id": "tulip_results_debug",
"type": "debug",
"name": "Tulip response",
"x": 800, "y": 200, "wires": []
},
{
"id": "tulip_recipe_in",
"type": "http in",
"name": "POST /change-recipe",
"url": "/change-recipe",
"method": "post",
"x": 160, "y": 300, "wires": [["tulip_recipe_format"]]
},
{
"id": "tulip_recipe_format",
"type": "function",
"name": "Format request",
"func": "const id = msg.payload?.id;\nif (!id) { msg.statusCode = 400; msg.payload = {error:'missing id'}; return msg; }\nmsg.headers = {'Content-Type':'application/json'};\nmsg.payload = JSON.stringify({id});\nmsg.method = 'POST';\nreturn msg;",
"x": 380, "y": 300, "wires": [["tulip_recipe_call"]]
},
{
"id": "tulip_recipe_call",
"type": "http request",
"name": "/pipeline/activate",
"method": "POST",
"url": "localhost:5001/pipeline/activate",
"ret": "obj",
"x": 600, "y": 300, "wires": [["tulip_recipe_resp"]]
},
{
"id": "tulip_recipe_resp",
"type": "http response",
"name": "to Tulip",
"x": 800, "y": 300, "wires": []
}
]
Aún necesitará conectar el nodo All Block Outputs a tulip_results_format (no está en el JSON anterior porque ese nodo es específico de OV y ya existe en cada flujo).
Solución de problemas
| Síntoma | Causa / solución |
|---|---|
Tulip Connector devuelve connection refused | La cámara y el Tulip Edge Device no están en la misma red, o el firewall de la cámara está bloqueando el puerto 80. Ambos deben ser accesibles entre sí; haga ping a la cámara desde el Tulip Edge Device primero. |
| El HTTP IN de activación nunca se dispara | Olvidó hacer Deploy en Node-RED después de agregar el nodo, o la URL tiene un error tipográfico (debe comenzar con /, no con /node-red/). |
| El cambio de receta devuelve 404 | Endpoint incorrecto para su firmware. Pre-v18.92 usa /edge/pipeline/activate desde la IP de la cámara; v18.92+ usa localhost:5001/pipeline/activate desde dentro de Node-RED. |
/live-feed muestra una imagen en negro en Tulip | La cámara aún no ha tomado una captura, o la imagen no se ha actualizado. Anexe ?t={{Date.now()}} para forzar al Image Widget a volver a obtenerla. |
| Tulip recibe un 200 pero sin cuerpo de resultado | La cámara dispara el trigger e inmediatamente devuelve 200 — el resultado regresa de forma asíncrona a través del flujo de envío de resultados (Sección 2). Asegúrese de que ese flujo esté conectado y desplegado. |
| Los resultados por ROI son ruidosos / Tulip solo quiere aprobado/rechazado | Por eso el nodo Function consolida todo en un único campo `result: "pass" |
Qué sigue
- Variables de Entorno — mantenga
TULIP_ENDPOINTy otras configuraciones específicas de implementación fuera del flujo. - Conectar al PLC (EtherNet/IP & PROFINET) — para integración paralela del PLC junto con Tulip.
- Bloques Personalizados de Node-RED de Overview — referencia para los nodos de la paleta específicos de OV (Trigger, All Block Outputs, etc.).
- Configuración de Salidas (Paso 5) — donde Node-RED encaja en el flujo más amplio de aprobado/rechazado.