跳到主要內容

AI 驅動文件

您想了解什麼?

程式指令碼

程式指令碼是一小段 JavaScript 程式碼,在某個程式每次檢測的所有工具執行之後執行。它可以:

  • 使用您自己的邏輯判定通過或失敗,並說明原因
  • 寫入您自己的位元組到 PLC 的 User Results
  • 將後設資料和標籤與捕獲一同儲存,以便您可以在庫中查詢

每個程式最多有一個指令碼,並且該指令碼會隨程式一起匯出。

開啟編輯器​

在程式的 判定 步驟中,滾動到 程式指令碼,然後單擊 開啟指令碼編輯器。

判定步驟:通過/失敗由規則或程式指令碼決定,附有每次檢測執行流程的示意圖

在判定步驟的頂部,您可以選擇由誰決定判定結果:規則或程式指令碼。規則始終執行,指令碼可以看到它們的判定結果。如果您將設定保留為規則,則指令碼無法更改通過或失敗,但其原因、後設資料、標籤和 PLC 位元組仍然適用。這是一種安全的入門方式:新增指令碼用於設定原因和位元組,並讓規則主導判定。

程式指令碼編輯器

執行方式​

Trigger → Capture and tools → Pass/fail rules → inspect(input) → Verdict, PLC User Results, Library, MQTT

指令碼匯出一個函式 inspect(input)。相機在該程式的每次檢測中,在工具和規則之後呼叫它。產線會等待它完成,因此它必須同步且快速:預設有 25 毫秒,在指令碼的設定中最多可達 200 毫秒,其耗時會計入節拍時間。

input 包含到目前為止的所有資訊。它是隻讀的:

欄位含義
tools每個工具的結果,按工具執行的順序排列
tools_by_name按工具名稱索引的相同結果
rules規則的判定結果:judgment、pass、not_inspected
vars按名稱索引的變數。沒有值的變數為 null
vars_info每個變數的資訊:received_at、age_ms 和 source
recipeid、uuid 和 name
triggerid、source(例如 plc、gpio、http、manual)和 metadata

每個工具結果都有 name、type、judgment、pass、score、plc_slot、execute_ms、error、skipped_by,以及一個 detail 物件,其結構取決於具體的工具。在讀取 detail 之前先檢查 type,例如 ai_fast_classifier(帶有 detail.regions)、ai_ocr(帶有 detail.fields)、ai_counting(帶有 detail.count)或 classical.barcode(帶有 detail.data)。

返回值​

每個欄位都是可選的。省略的欄位會保持相機的正常行為,因此指令碼可以僅設定原因並將判定留給規則。不返回任何內容則不更改任何內容。

欄位含義限制
judgment"pass"、"fail" 或 "not_inspected"(true、false 和 null 含義相同)。僅在判定步驟設定為程式指令碼時使用
reason顯示在庫中並在結果訊息(MQTT、HTTP API、相機指令碼)中傳送的文字64 個字元
plc_user_results傳送到 PLC 的 User Results 的位元組,通常使用 pack() 構建已配置的 User Results 大小
metadata與捕獲一同儲存並顯示在其庫詳情中的鍵值對。鍵由字母、數字和下劃線組成,以字母開頭;值為文字、數字或 true/false32 個鍵,文字值最多 256 個字元
tags在庫中顯示為標籤晶片的標籤,可用於篩選16 個標籤,每個 32 個字元

export function inspect({ tools_by_name, vars }) {

const ocr = tools_by_name["Serial"];
if (ocr?.type !== "ai_ocr" || !ocr.detail) {
return { judgment: false, reason: "no OCR result" };
}
const expected = vars["serial"];
if (typeof expected !== "string") {
return { judgment: null, reason: "serial not received" };
}
const read = ocr.detail.fields.map((f) => f.text_judged).join("");
const match = read === expected;
return { judgment: match, reason: match ? "serial matches" : "serial differs" };
}

試一試​

程式指令碼除錯臺

更換部件、編輯指令碼,然後按執行。指令碼接收與相機傳給 inspect() 相同的輸入,並在相同的規則下執行。

示例部件
通過/失敗由以下方式決定
現場匯流排

一個具有 10 個區域的 AI 分類工具,每個引腳一個。點選引腳可翻轉其狀態。

當前檢測結果
規則規定: 失敗
顯示輸入物件
{
  "recipe": {
    "id": 3,
    "uuid": "01a15a44-9a3c-72bb-ab4f-5481cce20093",
    "name": "Sample recipe"
  },
  "trigger": {
    "id": 4182,
    "source": "plc",
    "metadata": {}
  },
  "rules": {
    "judgment": "fail",
    "pass": false,
    "not_inspected": false
  },
  "vars": {},
  "vars_info": {},
  "tools": [
    {
      "type": "ai_fast_classifier",
      "id": "01a1-pins",
      "name": "Pins",
      "plc_slot": 1,
      "judgment": "fail",
      "pass": false,
      "score": 0.9,
      "execute_ms": 6,
      "error": "none",
      "skipped_by": "",
      "detail": {
        "regions": [
          {
            "label": "Pin 1",
            "class_label": "seated",
            "confidence": 0.97,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 2",
            "class_label": "seated",
            "confidence": 0.93,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 3",
            "class_label": "seated",
            "confidence": 0.95,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 4",
            "class_label": "bent",
            "confidence": 0.81,
            "verdict": "fail",
            "threshold": 0.5,
            "pass": false
          },
          {
            "label": "Pin 5",
            "class_label": "seated",
            "confidence": 0.91,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 6",
            "class_label": "seated",
            "confidence": 0.96,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 7",
            "class_label": "seated",
            "confidence": 0.94,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 8",
            "class_label": "seated",
            "confidence": 0.89,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 9",
            "class_label": "seated",
            "confidence": 0.98,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 10",
            "class_label": "seated",
            "confidence": 0.92,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          }
        ],
        "overall": "fail"
      }
    }
  ],
  "tools_by_name": {
    "Pins": {
      "type": "ai_fast_classifier",
      "id": "01a1-pins",
      "name": "Pins",
      "plc_slot": 1,
      "judgment": "fail",
      "pass": false,
      "score": 0.9,
      "execute_ms": 6,
      "error": "none",
      "skipped_by": "",
      "detail": {
        "regions": [
          {
            "label": "Pin 1",
            "class_label": "seated",
            "confidence": 0.97,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 2",
            "class_label": "seated",
            "confidence": 0.93,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 3",
            "class_label": "seated",
            "confidence": 0.95,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 4",
            "class_label": "bent",
            "confidence": 0.81,
            "verdict": "fail",
            "threshold": 0.5,
            "pass": false
          },
          {
            "label": "Pin 5",
            "class_label": "seated",
            "confidence": 0.91,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 6",
            "class_label": "seated",
            "confidence": 0.96,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 7",
            "class_label": "seated",
            "confidence": 0.94,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 8",
            "class_label": "seated",
            "confidence": 0.89,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 9",
            "class_label": "seated",
            "confidence": 0.98,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          },
          {
            "label": "Pin 10",
            "class_label": "seated",
            "confidence": 0.92,
            "verdict": "pass",
            "threshold": 0.5,
            "pass": true
          }
        ],
        "overall": "fail"
      }
    }
  }
}
程式指令碼

按執行以檢視此檢測最終的結果。

瀏覽器中的模擬。在相機上,儲存之前請使用指令碼編輯器中的 Test 對實際捕獲的影象執行草稿。

為 PLC 寫入位元組​

返回 plc_user_results,使用 pack(layout, values) 構建。PLC 會在 Inspection Completed 邊沿之前隨結果一起讀取它。

將位元組結構一次性描述為佈局(layout):一個由 { name, type } 欄位組成的有序列表。

型別位元組數
u8、i8、bool1
u16le、u16be、i16le、i16be2
u32le、u32be、i32le、i32be、f32le、f32be4
u64le、u64be、i64le、i64be、f64le、f64be8
帶 count 的 bits每個值佔一位,第一個值位於最低位
帶 length 的 ascii 或 utf8文字,按長度填充或(當 truncate: true 時)截斷
帶 length 的 bytes原始位元組
帶 length 的 pad零位元組,用於跳過間隙
const OUT = [
{ name: "regionOk", type: "bits", count: 80 },
{ name: "count", type: "u16le" },
];

export function inspect({ tools_by_name }) {

const classifier = tools_by_name["Classifier"];
if (classifier?.type !== "ai_fast_classifier" || !classifier.detail) return;
const regionOk = classifier.detail.regions.map((r) => r.pass).slice(0, 80);
return { plc_user_results: pack(OUT, { regionOk, count: regionOk.length }) };
}
匹配現場匯流排位元組序

每種多位元組型別都指定了自己的位元組序,相機不會自動為您交換位元組。在 EtherNet/IP 上使用 le 型別,在 PROFINET 上使用 be 型別。當型別與相機的現場匯流排不匹配時,指令碼檢查會發出警告。

  • 位元組 0 在 EtherNet/IP 上位於輸入元件的第 288 位元組,在 PROFINET 上位於 User Results 模組的第 0 位元組。該區域的其餘部分會被清零
  • 當某個值無法容納到其欄位中時,pack 會丟擲 RangeError。請在儲存前用一個失敗件進行測試
  • unpack(layout, bytes) 可將位元組重新讀回,crc(bytes, "crc16-modbus" | "crc32") 可計算校驗和

出現問題時​

如果 inspect 丟擲異常、超時、超出記憶體(8 MiB),或返回了相機無法接受的內容(例如原因超過 64 個字元或 judgment: "PASS"):

  • 該部件會被標記為 Not inspected(未檢測)
  • PLC User Results 被清零
  • 它返回的任何內容(原因、後設資料、標籤)都不會被使用
  • 錯誤會與捕獲一起儲存,您可以在庫中檢視
在相機指令碼中保持狀態

模組級保留的值僅在程式重新載入前有效,例如儲存之後。不要在程式指令碼中計數部件或保留歷史記錄。請使用相機指令碼,它會在事件之間保持狀態。

程式指令碼不能做什麼​

  • 沒有網路、沒有計時器、也不能等待:沒有 fetch、setTimeout、import 或 eval,並且 inspect 不能為 async
  • 它看不到影象本身,只能看到各工具的結果
  • 它只能通過 input 和變數讀取外部資訊

模板​

Templates 庫包含 14 個可用指令碼,按任務分組。選擇一個,填入它使用的工具或變數,然後點選 Insert template。

Recipe script Templates gallery

分組模板
判定通過或失敗當足夠多的區域通過時判為通過 · 序列號與 OCR 文字匹配 · 條碼等於某個變數 · 計數在某個變數的容差範圍內
選擇何時檢測跳過其他部件型別 · 當變數缺失或過時時跳過
向 PLC 傳送資料以 16 位數字傳送計數 · 打包各區域的通過/失敗標誌 · 為每個工具傳送一個結果程式碼
解釋結果指出失敗的工具名稱 · 將計數寫入原因
標記捕獲新增後設資料和複查標籤 · 為臨界件打上覆查標籤 · 為每次捕獲儲存一個變數

測試與歷史記錄​

  • 測試可在實時觸發上執行草稿,也可在已儲存的捕獲上執行:最新的一張、最近 N 張(最多 500 張)、庫篩選結果或指定捕獲編號。它會顯示草稿的判定與每張捕獲記錄的判定不一致的位置,讓您在上線前就能看到它會做出什麼決定
  • 歷史記錄保留您最近 20 個已儲存版本
從規則模式開始

在判定步驟仍處於規則模式時編寫指令碼,並在已儲存的捕獲上使用測試,將其判定與已記錄的判定進行比較。僅當兩者在您希望出現分歧的位置出現分歧時,才切換到程式指令碼。

後續內容​