跳到主要内容

AI 驱动文档

您想了解什么?

程序脚本

程序脚本是一段简短的 JavaScript 代码,在某个程序的每次检测中,在工具运行后执行。它可以:

  • 用您自己的逻辑决定通过或失败,并说明原因
  • 为 PLC 的 User Results 写入您自己的字节
  • 随捕获保存元数据和标签,以便您在库中查找

每个程序最多有一个脚本,并随程序一起导出。

打开编辑器​

在程序的 判定 步骤中,滚动到 程序脚本 并点击 打开脚本编辑器。

判定步骤:通过/失败由规则或程序脚本决定,附带每次检测时运行内容的图示

在判定步骤的顶部,您可以选择由什么决定判定结果:规则 或 程序脚本。规则始终运行,脚本会看到规则的判定结果。如果将设置保留为 规则,脚本无法更改通过或失败,但其原因、元数据、标签和 PLC 字节仍然生效。这是一个安全的起点:添加脚本以提供原因和字节,并让规则主导判定。

程序脚本编辑器

运行方式​

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

脚本导出一个函数 inspect(input)。相机在程序的每次检测中,在工具和规则之后调用它。产线会等待其执行完成,因此它必须是 同步且快速的:默认有 25 ms,在脚本的 设置 中最多可达 200 ms,其耗时会计入节拍时间。

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_resultsPLC 的 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 会在检测完成边沿之前随结果一起读取。

将字节描述为一个布局:由 { 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"):

  • 该部件被标记为未检测
  • PLC User Results 被清零
  • 它返回的任何内容(原因、元数据、标签)都不会被使用
  • 错误与捕获一起保存,因此您可以在库中查看
在相机脚本中保存状态

在模块级别保存的值只能存留到程序重新加载前,例如保存之后。不要在程序脚本中计数部件或保存历史记录。请使用相机脚本,它可以在事件之间保持状态。

程序脚本不能做什么​

  • 没有网络、没有定时器、也不能等待:没有 fetch、setTimeout、import 或 eval,而且 inspect 不能是 async
  • 它无法看到图像本身,只能看到工具的结果
  • 它只能通过 input 和变量读取外部世界

模板​

模板库中有 14 个可用脚本,按任务分组。选择一个,填入它所使用的工具或变量,然后点击插入模板。

程序脚本模板库

分组模板
判定通过或失败足够多区域通过时判为通过 · 序列号与 OCR 文本匹配 · 条码等于变量 · 计数在变量的容差范围内
选择何时检测跳过其他部件类型 · 当变量缺失或过时时跳过
向 PLC 发送数据以 16 位数字发送计数 · 打包区域通过/失败标志 · 为每个工具发送结果代码
解释结果指明失败的工具 · 将计数放入原因中
标记捕获添加元数据和审核标签 · 为临界样本添加审核标签 · 为每次捕获保存一个变量

测试与历史记录​

  • 测试 可在实时触发上运行草稿,也可在已存储的捕获上运行:最新的一张、最近 N 张(最多 500 张)、库筛选结果或捕获编号。它会显示草稿的判定与每张捕获所记录的判定之间的差异,让您在正式上线前看到它会做出什么决定
  • 历史记录 会保留您最近 20 个已保存的版本
从规则模式开始

在判定步骤仍处于规则模式时编写脚本,并对已存储的捕获使用测试,将其判定与已记录的判定进行比较。仅当它们在您期望的位置出现分歧时,才切换到程序脚本。

下一步​