Skip to main content

AI-POWERED DOCS

What do you want to know?

Kết Nối với Tulip

Tulip là một nền tảng MES / vận hành tuyến đầu kết nối không cần code. Camera OV10i kết nối với Tulip thông qua Node-RED chạy trên camera — cùng một trình soạn thảo flow mà bạn sử dụng cho PLC, MQTT và các tích hợp I/O khác. Tulip có thể:

  • Trigger một lần kiểm tra từ nút bấm của người vận hành hoặc sự kiện ứng dụng.
  • Nhận kết quả pass/fail theo từng ROI và toàn cục cùng với metadata thu thập được.
  • Chuyển recipe đang hoạt động khi người vận hành quét mã vạch của bộ phận hoặc chọn một sản phẩm khác.
  • Hiển thị hình ảnh trực tiếp (với lớp phủ bounding-box) bên trong ứng dụng Tulip để người vận hành thấy được những gì AI đã thấy.

Hướng dẫn này sẽ đi qua từng điểm tích hợp trong bốn điểm trên một cách toàn diện.

Kiến Trúc

┌────────────────────────────┐               ┌──────────────────────────────┐
│ TULIP │ │ OV10i │
│ │ │ │
│ ┌──────────────────┐ │ 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>} │ │
│ └──────────────────┘ │ │ └──────────────────────┘ │
└────────────────────────────┘ └──────────────────────────────┘

Cầu nối ở giữa luôn là Node-RED chạy bên trong IO Block của camera — đây là nơi bạn xây dựng cả bốn tích hợp.

Canvas Node-RED bên trong IO Block của camera — Advanced Mode của giai đoạn IO Logic

Sử dụng biến môi trường cho endpoint Tulip

Việc hard-code URL Tulip Connector vào flow Node-RED sẽ ràng buộc flow với một lần triển khai duy nhất. Hãy định nghĩa biến môi trường TULIP_ENDPOINT trong System Settings → Environment Variables, sau đó đọc nó từ các HTTP Request node bằng ${TULIP_ENDPOINT}. Điều này cho phép bạn export cùng một flow và import lại trên mọi camera trong dây chuyền. Xem Environment Variables để biết toàn bộ mẫu.

Chọn phương thức truyền tải: HTTP hoặc MQTT

Tùy chọnKhi nào sử dụngGhi chú
HTTP (khuyến nghị cho hầu hết các triển khai Tulip)Tulip Connector Functions gọi các HTTP endpoint một cách tự nhiên. Đây là cách đơn giản nhất để kích hoạt từ một nút widget Tulip.Hoạt động mà không cần hạ tầng bổ sung. Tulip → camera và camera → Tulip đều sử dụng POST thuần túy.
MQTTNhà máy của bạn đã chạy một MQTT broker (Sparkplug B, IT/OT broker, v.v.) và Tulip Edge Connector được cấu hình để giao tiếp với nó.Độ trễ thấp hơn cho tín hiệu khối lượng cao. Yêu cầu một broker.
OPC UABạn đang chạy một instance Tulip Edge MC với OPC UA Connector.OV10i được cài đặt sẵn các Node-RED block OPC UA.

Phần còn lại của hướng dẫn này sử dụng HTTP trong các ví dụ — đây là phương thức mặc định cho hầu hết các triển khai Tulip. Các mẫu tương tự cũng áp dụng cho MQTT (thay thế node HTTP IN / HTTP Request bằng node MQTT in / MQTT out).

1. Kích hoạt camera từ một ứng dụng Tulip

Một thao tác viên Tulip nhấn nút → Connector Function của Tulip gọi một HTTP endpoint mà Node-RED của camera đang lắng nghe → Node-RED kích hoạt việc chụp ảnh.

Phía Node-RED: nhận tín hiệu kích hoạt

Trong IO Block của camera (Advanced Mode → Node-RED), xây dựng:

[ HTTP IN: POST /trigger ]──▶[ Trigger node ]──▶[ HTTP Response: 200 ok ]
  1. Kéo vào một node http in từ danh mục network.
    • Method: POST
    • URL: /trigger
  2. Kết nối nó với node Trigger đặc thù của OV (dưới block logic trong bảng OV). Node Trigger sẽ kích hoạt pipeline chụp ảnh của camera.
  3. Thêm một node http response để Connector của Tulip nhận được phản hồi 200 OK ngay lập tức (đừng để Tulip chờ kết quả AI inference — hãy để kết quả được trả về bất đồng bộ, xem Phần 2).
  4. Nhấp Deploy.

Endpoint này hiện đã hoạt động tại http://<CAMERA_IP>/node-red/trigger (HTTP root của Node-RED được mount dưới /node-red/).

Phía Tulip: gọi tín hiệu kích hoạt từ một nút

Trong tài khoản Tulip của bạn:

  1. Đi đến Apps → Connectors và tạo một HTTP Connector mới nếu bạn chưa có.
  2. Đặt Host là IP của camera và Port80 (hoặc 443 nếu bạn đã bật HTTPS).
  3. Tạo một Connector Function mới có tên Trigger Camera:
    • Method: POST
    • Endpoint: /node-red/trigger
    • Không cần body hoặc tham số đầu vào cho một trigger cơ bản — camera sẽ sử dụng recipe đang hoạt động.
  4. Lưu và kiểm tra Connector Function. Bạn sẽ thấy phản hồi 200 và camera sẽ chụp một hình ảnh.
  5. Trong ứng dụng Tulip của bạn, thêm một widget Button. Trong action của nó, chọn Run Connector Function → Trigger Camera.

Khi thao tác viên nhấn nút, camera sẽ kích hoạt.

Truyền thêm ngữ cảnh cùng với trigger

Nếu bạn muốn gửi số serial hoặc ID của bộ phận với mỗi trigger, hãy thay đổi Tulip Connector Function để chấp nhận đầu vào (ví dụ: serial_number) và đặt chúng vào request body. Ở phía camera, node HTTP IN của bạn sẽ nhận chúng dưới dạng msg.payload và bạn có thể lưu chúng vào một biến flow để flow gửi kết quả (Phần 2) có thể đính kèm chúng vào báo cáo kiểm tra.

2. Gửi kết quả trở lại Tulip

Sau khi camera chụp ảnh, node All Block Outputs sẽ kích hoạt với kết quả kiểm tra đầy đủ. Mẫu thực hiện:

[ All Block Outputs ]──▶[ Function: format for Tulip ]──▶[ HTTP Request: POST to Tulip ]

Xây dựng flow

  1. Kết nối một node function vào sau node All Block Outputs.
  2. Hàm này hợp nhất kết quả của từng ROI thành một kết quả pass/fail tổng thể duy nhất và rút gọn payload chỉ còn những gì Tulip thực sự cần:
// 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;
  1. Kết nối nó vào một node http request:
    • Method: POST (hoặc sử dụng msg.method từ hàm ở trên)
    • URL: ${TULIP_ENDPOINT} (đọc từ biến môi trường) hoặc URL Tulip Connector được mã hóa cứng
    • Return: a UTF-8 string (hoặc JSON nếu endpoint Tulip của bạn trả về JSON)
  2. Kết nối một node debug sau http request để bạn có thể kiểm tra phản hồi của Tulip trong khi thử nghiệm.
  3. Nhấp vào Deploy.

Phía Tulip: nhận kết quả

Trong Tulip:

  1. Tạo một HTTP Connector Function theo hướng nhận — một webhook endpoint, hoặc sử dụng Tables API của Tulip để ghi kết quả vào một Tulip Table.
  2. Cấu hình logic của ứng dụng Tulip để phản ứng với payload đầu vào (ví dụ: cập nhật chỉ báo trạng thái, ghi log vào Table, tăng giá trị bộ đếm).

Đối với Tulip Tables cụ thể, mẫu thực hiện là POST đến https://<your-tulip-instance>.tulip.co/api/v3/tables/<table_id>/records với các trường result, serial_number, timestamp. Sử dụng Tulip API token trong header Authorization — lưu token như một credential trên HTTP Connector, không lưu trong flow của Node-RED.

3. Thay đổi recipe từ Tulip

Người vận hành thường cần chuyển sang một recipe khác khi loại sản phẩm thay đổi (quét barcode, dropdown, dispatch). Camera cung cấp một HTTP endpoint tích hợp sẵn để kích hoạt một recipe theo ID.

Tìm ID của recipe

  1. Mở recipe trong Recipe Editor.
  2. Nhìn vào thanh địa chỉ trình duyệt: /recipes/15/editor → ID là 15.

Xây dựng flow chuyển đổi recipe

[ HTTP IN: POST /change-recipe ]──▶[ Function: format request ]──▶[ HTTP Request: POST localhost:5001/pipeline/activate ]──▶[ HTTP Response ]
  1. Node HTTP IN: POST /change-recipe. Tulip sẽ gọi đến endpoint này với body dạng { "id": 15 }.
  2. Node Function — chuyển recipe ID đến API nội bộ của camera:
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;
  1. Node HTTP Request:
    • Method: POST
    • URL: localhost:5001/pipeline/activate
    • Return: a parsed JSON object
  2. Node HTTP Response để Connector Function của Tulip nhận lại status code.
URL API nội bộ của camera

Bên trong Node-RED trên camera, endpoint chuyển đổi recipe là localhost:5001/pipeline/activate — đây là API admin nội bộ của camera. Bạn đang proxy yêu cầu của Tulip thông qua Node-RED đến API nội bộ. Không nên gọi trực tiếp đến port 5001 từ bên ngoài camera; nó chỉ được mở cho các flow chạy bên trong camera.

Đối với firmware cũ hơn (trước v18.92), URL là http://[CAMERA_IP]/edge/pipeline/activate. Nếu bạn đang dùng firmware cũ và localhost:5001 không phản hồi, hãy chuyển về URL của v17/v18. Cập nhật firmware camera lên v18.92+ để sử dụng đường dẫn mới.

Phía Tulip: gọi chuyển đổi recipe

  1. Thêm một Connector Function Switch Recipe trên cùng HTTP Connector.
  2. Method: POST, Endpoint: /node-red/change-recipe.
  3. Thêm một input parameter recipe_id (number).
  4. Đặt request body thành { "id": $recipe_id }.
  5. Trong ứng dụng Tulip của bạn, kết nối trigger Barcode Scanner hoặc widget Dropdown để gọi hàm này với recipe ID tương ứng.

Người vận hành quét mã vạch → Tulip tra cứu recipe ID cho bộ phận đó → Connector Function gọi đến camera → camera chuyển đổi recipe trong chưa đầy một giây.

4. Hiển thị hình ảnh trực tiếp (với bounding box) trong Tulip

Image Widget của Tulip có thể hiển thị bất kỳ URL nào trả về một hình ảnh. OV10i có một URL HMI tích hợp sẵn trả về ảnh chụp gần nhất với các lớp phủ bounding-box và ROI được vẽ lên trên:

http://<CAMERA_IP>/live-feed

/live-feed hiển thị hình ảnh camera với lớp phủ ROI (các bounding box được vẽ)

Để nhúng vào ứng dụng Tulip:

  1. Thêm một Image Widget vào step Tulip của bạn.
  2. Đặt Image URL thành một biến Tulip mà bạn điền từ kết quả inspection, hoặc thành một URL tĩnh nếu mỗi camera có step dashboard riêng.
  3. Cách đơn giản nhất: hard-code http://<CAMERA_IP>/live-feed và để nó tự động refresh sau mỗi lần chụp (image widget của Tulip refresh khi URL hoặc một query param thay đổi — thêm ?t={{timestamp}} để buộc refresh).

Nếu bạn muốn ảnh chụp gốc (không có bounding box), hãy sử dụng image_url từ msg.payload — đó là URL theo từng lần chụp trỏ đến hình ảnh đã lưu trong thư viện của camera.

Một step Tulip, nhiều camera

Nếu bạn có nhiều camera trên cùng một dây chuyền, mỗi camera có IP riêng, hãy lưu URL của camera trong một biến Tulip cho mỗi step ứng dụng và để người vận hành chọn trạm nào để xem. Image Widget sẽ chuyển đổi ngay lập tức khi biến thay đổi.

Reference flow (importable Node-RED JSON)

Flow bên dưới kết hợp cả bốn tích hợp: trigger HTTP IN, HTTP Request gửi kết quả, recipe switcher, và một debug node. Sao chép JSON, mở Node-RED → menu hamburger → Import, và dán vào.

[
{
"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": []
}
]

Bạn vẫn cần kết nối node All Block Outputs vào tulip_results_format (nó không có trong JSON ở trên vì node đó là đặc thù của OV và đã tồn tại sẵn trên mọi flow).

Khắc Phục Sự Cố

Triệu chứngNguyên nhân / cách khắc phục
Tulip Connector trả về connection refusedCamera và Tulip Edge Device không cùng mạng, hoặc firewall của camera đang chặn cổng 80. Cả hai phải có thể truy cập lẫn nhau; hãy ping camera từ Tulip Edge Device trước.
Trigger HTTP IN không bao giờ kích hoạtQuên Deploy trong Node-RED sau khi thêm node, hoặc URL bị sai chính tả (phải bắt đầu bằng /, không phải /node-red/).
Recipe switch trả về 404Sai endpoint cho firmware của bạn. Phiên bản trước v18.92 sử dụng /edge/pipeline/activate từ IP của camera; v18.92+ sử dụng localhost:5001/pipeline/activate từ bên trong Node-RED.
/live-feed hiển thị hình ảnh đen trong TulipCamera chưa chụp ảnh, hoặc hình ảnh chưa được làm mới. Thêm ?t={{Date.now()}} để buộc Image Widget tải lại.
Tulip nhận được 200 nhưng không có nội dung kết quảCamera kích hoạt trigger và trả về 200 ngay lập tức — kết quả được trả về bất đồng bộ qua flow gửi kết quả (Mục 2). Đảm bảo flow đó đã được kết nối và triển khai.
Kết quả theo từng ROI quá nhiễu / Tulip chỉ cần pass/failĐó là lý do node Function hợp nhất mọi thứ vào một trường duy nhất `result: "pass"

Tiếp theo là gì