AI-POWERED DOCS
What do you want to know?
Kết Nối Với Tulip
Tulip là nền tảng MES / vận hành tuyến đầu được kết nối không cần code. Camera OV80i kết nối với Tulip thông qua Node-RED chạy trên camera — cùng một trình chỉnh sửa flow mà bạn sử dụng cho PLC, MQTT và các tích hợp I/O khác. Tulip có thể:
- Kích hoạt 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 chụp ảnh.
- Chuyển đổi 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 các lớp phủ bounding-box) bên trong ứng dụng Tulip để người vận hành thấy những gì AI đã thấy.
Hướng dẫn này sẽ trình bày chi tiết từng điểm trong bốn điểm tích hợp đó.
Kiến Trúc
┌────────────────────────────┐ ┌──────────────────────────────┐
│ TULIP │ │ OV80i │
│ │ │ │
│ ┌──────────────────┐ │ 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.

Việc hard-code URL Tulip Connector vào flow Node-RED sẽ ràng buộc flow với một 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 node HTTP Request bằng ${TULIP_ENDPOINT}. Điều này cho phép bạn xuất cùng một flow và nhập lại trên mọi camera trong dây chuyền. Xem Environment Variables để biết mẫu đầy đủ.
Lựa chọn phương thức truyền: HTTP hoặc MQTT
| Tùy chọn | Khi nào sử dụng | Ghi chú |
|---|---|---|
| HTTP (được 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 bất kỳ hạ tầng bổ sung nào. Tulip → camera và camera → Tulip đều sử dụng POST đơn giản. |
| MQTT | Nhà máy của bạn đã chạy sẵn 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 việc truyền tín hiệu khối lượng lớn. Yêu cầu một broker. |
| OPC UA | Bạn đang chạy một instance Tulip Edge MC với OPC UA Connector. | OV80i được cài đặt sẵn các block OPC UA Node-RED. |
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ự áp dụng cho MQTT (thay thế các node HTTP IN / HTTP Request bằng các node MQTT in / MQTT out).
1. Kích hoạt camera từ một ứng dụng Tulip
Một operator Tulip nhấn một nút → Tulip's Connector Function 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 trigger
Trong IO Block của camera (Advanced Mode → Node-RED), xây dựng:
[ HTTP IN: POST /trigger ]──▶[ Trigger node ]──▶[ HTTP Response: 200 ok ]
- Kéo vào một node http in từ danh mục network.
- Method:
POST - URL:
/trigger
- Method:
- Nối nó vào node Trigger đặc thù của OV (nằm trong block logic trong palette OV). Node Trigger sẽ kích hoạt pipeline chụp ảnh của camera.
- Thêm một node http response để Tulip's Connector nhận được phản hồi
200 OKngay lập tức (đừng để Tulip phải chờ kết quả suy luận AI — hãy để kết quả truyền về một cách bất đồng bộ, xem Phần 2). - Nhấp Deploy.
Endpoint 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 trigger từ một nút
Trong tài khoản Tulip của bạn:
- Đi đến Apps → Connectors và tạo một HTTP Connector mới nếu bạn chưa có.
- Đặt Host là IP của camera và Port là
80(hoặc443nếu bạn đã bật HTTPS). - 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.
- Method:
- Lưu và kiểm thử Connector Function. Bạn sẽ thấy phản hồi 200 và camera sẽ chụp một hình ảnh.
- 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 operator nhấn nút, camera sẽ kích hoạt.
Nếu bạn muốn gửi một serial number hoặc part ID 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 nhận chúng dưới dạng msg.payload và bạn có thể lưu chúng vào một flow variable để 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, node All Block Outputs sẽ kích hoạt với kết quả kiểm tra đầy đủ. Mẫu:
[ All Block Outputs ]──▶[ Function: format for Tulip ]──▶[ HTTP Request: POST to Tulip ]
Xây dựng luồng
- Kết nối một node function ở phía sau node All Block Outputs.
- Function này hợp nhất các kết quả của từng ROI thành một kết quả pass/fail tổng thể và cắt payload xuống chỉ 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;
- Kết nối vào một node http request:
- Method:
POST(hoặc sử dụngmsg.methodtừ function ở 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)
- Method:
- Kết nối một node debug sau yêu cầu HTTP để bạn có thể kiểm tra phản hồi của Tulip trong khi thử nghiệm.
- Nhấp vào Deploy.
Phía Tulip: nhận kết quả
Trong Tulip:
- 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.
- Cấu hình logic của ứng dụng Tulip để phản ứng với payload đến (ví dụ: cập nhật chỉ báo trạng thái, ghi log vào Table, tăng bộ đếm).
Đối với Tulip Tables cụ thể, mẫu là POST tới 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 trữ token dưới dạng credential trên HTTP Connector, không phải trong luồng 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 mã vạch, dropdown, dispatch). Camera cung cấp một HTTP endpoint tích hợp sẵn để kích hoạt recipe theo ID.
Tìm ID của recipe
- Mở recipe trong Recipe Editor.
- 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 ]
- HTTP IN node:
POST /change-recipe. Tulip sẽ gọi node này với body như{ "id": 15 }. - Function node — 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;
- HTTP Request node:
- Method:
POST - URL:
localhost:5001/pipeline/activate - Return:
a parsed JSON object
- Method:
- HTTP Response node để Connector Function của Tulip nhận lại mã trạng thái.
Bên trong Node-RED trên camera, endpoint chuyển đổi recipe là localhost:5001/pipeline/activate — đó là API quản trị nội bộ của camera. Bạn đang chuyển tiếp yêu cầu của Tulip qua Node-RED đến API cục bộ. Đừng cố gắng gọi trực tiếp port 5001 từ bên ngoài camera; nó chỉ được hiển thị cho các flow nội bộ 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 dùng URL của v17/v18. Cập nhật firmware camera lên v18.92+ để sử dụng đường dẫn hiện đại.
Phía Tulip: gọi chuyển đổi recipe
- Thêm một Connector Function
Switch Recipetrên cùng một HTTP Connector. - Method:
POST, Endpoint:/node-red/change-recipe. - Thêm một input parameter
recipe_id(số). - Đặt request body thành
{ "id": $recipe_id }. - 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. OV80i có URL HMI tích hợp sẵn trả về ảnh chụp gần nhất với bounding box và ROI overlay được vẽ chồng lên trên:
http://<CAMERA_IP>/live-feed

Để nhúng vào ứng dụng Tulip:
- Thêm một Image Widget vào step Tulip của bạn.
- Đặt Image URL thành một biến Tulip mà bạn điền từ kết quả kiểm tra, hoặc thành một URL tĩnh nếu mỗi camera có step dashboard riêng.
- Cách đơn giản nhất: hard-code
http://<CAMERA_IP>/live-feedvà để nó tự động làm mới mỗi lần chụp (image widget của Tulip làm mới khi URL hoặc query param thay đổi — thêm?t={{timestamp}}để buộc làm mới).
Nếu bạn muốn ảnh chụp gốc (không có bounding box), hãy dùng image_url từ msg.payload thay thế — đó là URL theo từng lần chụp trỏ đến ảnh đã lưu trong thư viện của 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 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 chuyển đổi ngay lập tức khi biến thay đổi.
Luồng tham khảo (Node-RED JSON có thể import)
Luồng dưới đây kết hợp cả bốn tích hợp: trigger HTTP IN, HTTP Request gửi kết quả, bộ chuyển đổi recipe 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 đó đặc thù cho OV và đã tồn tại sẵn trên mọi flow).
Khắc phục sự cố
| Triệu chứng | Nguyên nhân / cách khắc phục |
|---|---|
Tulip Connector trả về connection refused | Camera và Tulip Edge Device không ở cùng một mạng, hoặc tường lửa của camera đang chặn cổng 80. Cả hai phải có thể kết nối với nhau; trước tiên hãy ping camera từ Tulip Edge Device. |
| Trigger HTTP IN không bao giờ kích hoạt | Quê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/). |
| Chuyển đổi recipe trả về 404 | Sai 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 Tulip | Camera 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 phải tải lại. |
| Tulip nhận được mã 200 nhưng không có nội dung kết quả | Camera kích hoạt trigger và lập tức trả về 200 — 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à deploy. |
| Kết quả theo từng ROI bị nhiễu / Tulip chỉ cần pass/fail | Đó là lý do node Function gộp mọi thứ thành một trường duy nhất `result: "pass" |
Tiếp theo
- Environment Variables — giữ
TULIP_ENDPOINTvà các cấu hình đặc thù triển khai khác bên ngoài flow. - Connect to PLC (EtherNet/IP & PROFINET) — để tích hợp PLC song song cùng với Tulip.
- Overview Node-RED Custom Blocks — tài liệu tham khảo cho các node palette đặc thù của OV (Trigger, All Block Outputs, v.v.).
- Setting Up Outputs (Step 5) — vị trí của Node-RED trong flow pass/fail tổng thể.