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 được kết nối không cần lập trình (no-code). Camera OV20i 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 các tích hợp PLC, MQTT và I/O khác. Tulip có thể:
- Kích hoạt một cuộc 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 (kèm các 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 chi tiết.
Kiến Trúc
┌────────────────────────────┐ ┌──────────────────────────────┐
│ 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>} │ │
│ └──────────────────┘ │ │ └──────────────────────┘ │
└────────────────────────────┘ └──────────────────────────────┘
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 mã hóa cứng 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 của bạn 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 đủ.
Chọn phương thức truyền tải: HTTP hoặc MQTT
| Tùy chọn | Khi nào sử dụng | Ghi chú |
|---|---|---|
| HTTP (khuyến nghị cho hầu hết các triển khai Tulip) | Tulip Connector Functions gọi trực tiếp các HTTP endpoint. 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. Cả Tulip → camera và camera → Tulip đều sử dụng POST thông thường. |
| MQTT | Nhà 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 có 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. | OV20i được cài đặt sẵn các khối Node-RED 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 của 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ế 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
Người vận hành Tulip nhấn một 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 ]
- Kéo một node http in từ danh mục network.
- Method:
POST - URL:
/trigger
- Method:
- Kết nối nó với node Trigger chuyên dụng của OV (nằm trong block logic trong bảng OV). Node Trigger sẽ kích hoạt pipeline chụp của camera.
- Thêm một node http response để Connector của Tulip nhận lại ngay phản hồi
200 OK(không bắt Tulip phải chờ AI inference — hãy để kết quả trả về bất đồng bộ, xem Phần 2). - Nhấp Deploy.
Endpoint hiện đang hoạt động tại http://<CAMERA_IP>/node-red/trigger (HTTP root của Node-RED được gắn 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:
- Đi đến Apps → Connectors và tạo một HTTP Connector mới nếu bạn chưa có.
- Đặt Host thành IP của camera và Port thành
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ử dụng recipe đang hoạt động.
- Method:
- 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.
- 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 người vận hành chạm vào nút, camera sẽ kích hoạt.
Nếu bạn muốn gửi số serial hoặc part ID với mỗi lần kích hoạt, hãy thay đổi Tulip Connector Function để chấp nhận đầu vào (ví dụ: serial_number) và đặt chúng trong phần body của request. Ở 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 trong một biến flow để luồng 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:
[ All Block Outputs ]──▶[ Function: format for Tulip ]──▶[ HTTP Request: POST to Tulip ]
Xây dựng flow
- Kết nối một node function xuống dòng từ node All Block Outputs.
- Function này hợp nhất các kết quả theo từng ROI thành một kết quả pass/fail tổng thể và rút gọn payload xuống 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 HTTP request để 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 chiều 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 một Table, tăng giá trị bộ đếm).
Đối với Tulip Tables cụ thể, mẫu 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 trữ token làm credential trên HTTP Connector, không phải trong flow 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 sẵn một HTTP endpoint tích hợp để kích hoạt recipe theo ID.
Tìm recipe ID
- Mở recipe trong Recipe Editor.
- Xem 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 ]
- Node HTTP IN:
POST /change-recipe. Tulip sẽ gọi điều này với một body như{ "id": 15 }. - Node Function — truyền ID của recipe đế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;
- Node HTTP Request:
- Method:
POST - URL:
localhost:5001/pipeline/activate - Return:
a parsed JSON object
- Method:
- Node HTTP Response để 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 ủy quyền yêu cầu của Tulip qua Node-RED đến API cục bộ. Đừng cố gọi cổng 5001 trực tiếp từ bên ngoài camera; nó chỉ được hiển thị cho các flow 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 sử dụng firmware cũ và localhost:5001 không phản hồi, hãy quay lại sử dụng URL 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 HTTP Connector. - Method:
POST, Endpoint:/node-red/change-recipe. - Thêm một input parameter
recipe_id(number). - Đặt body của yêu cầu 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 ID recipe tương ứng.
Operator quét mã vạch → Tulip tra cứu ID recipe cho bộ phận đó → Connector Function gọi camera → camera chuyển đổi recipe trong vòng chưa đầy một giây.
4. Hiển thị hình ảnh trực tiếp (với bounding boxes) trong Tulip
Image Widget của Tulip có thể hiển thị bất kỳ URL nào trả về hình ảnh. OV20i 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ẽ ở trên:
http://<CAMERA_IP>/live-feed

Để nhúng nó 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.
- Mẫu đơ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 thô (không có bounding boxes), hãy sử dụng image_url từ msg.payload thay thế — đó là URL theo mỗi lần chụp trỏ đến hình ả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à để operator 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 đó dành riêng cho OV và đã tồn tại 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ạng, hoặc tường lửa của camera đang chặn cổng 80. Cả hai phải có thể truy cập lẫn 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 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 tải lại. |
| Tulip nhận được 200 nhưng không có result body | 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ả (Phần 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 hợp nhất mọi thứ thành một trường `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ù OV (Trigger, All Block Outputs, v.v.).
- Setting Up Outputs (Step 5) — vị trí Node-RED trong luồng pass/fail tổng thể.