node-red 通过递归的方式遍历 OPC UA 服务器一个目录下的所有标签组中的标签

在这里插入图片描述
这个方法的思路和主要代码由GPT生成。
需要安装 “node-red-contrib-opcua” 包来使用 OPC UA Browser。
在这个示例流程中,我们使用了以下节点:

  1. Inject 节点(用来触发流程开始):设置为每个流程启动时触发。
  2. OPC UA Browser 节点(用于获取目录信息):配置 OPC UA 服务器连接和目录路径。
  3. Function 节点(用于处理目录信息):用来解析 OPC UA Browser 返回的目录信息,并将标签名存储到一个数组中。
  4. Delay 节点(用于延迟发送标签组名):将标签组名发送到 OPC UA Browser 之前添加一些延迟。
  5. Debug 节点(用于调试输出):将标签名和标签组名输出到调试面板。

根据上边的流程截图创建流程,
Set browse address 按照图片样式设置一个需要办理访问的目录。
在这里插入图片描述

以下是 Function 节点 Decode & filter 的代码(用于解析目录信息并存储标签名):
在这里插入图片描述

//这段代码根据GPT生成的代码修改得到的。
// 定义两个空数组,用于存储不同类型的nodeId
var X_nodeIds = flow.get('X_nodeIds1') || [];
var nodeIds = flow.get('nodeIds1') || [];

// 获取消息中的节点数组
var items = msg.payload;

// 遍历节点数组中的每一个节点
for (var i = 0; i < items.length; i++) {
    var item = items[i];
    var ref = item.item;
    var nodeClass = ref.nodeClass;
    var typeDef = ref.typeDefinition;
    var bname = ref.browseName;
    var ns = bname.namespaceIndex;
    var name = bname.name;
    var nodeId = ref.nodeId;
    var NodeId_str = nodeId.toString();

    // 检查并跳过不满足条件的nodeId
    if (NodeId_str.includes('._') || NodeId_str.match(/^.*=_/) || NodeId_str.match('.V4') || NodeId_str.match('.@')) {
        continue;
    }

    // 根据不同的条件创建并发送消息
    if (ns === 2) {
        if (nodeClass === 1) { // 条件一:命名空间索引为2且nodeClass为1的节点
            var newmsgX = { topic: nodeId, payload: "", actiontype: 'browse' };
            // 发送第一个类型的消息
            node.send([null, newmsgX]);
            X_nodeIds.push(nodeId);
        } else if (nodeClass === 2) { // 条件二:命名空间索引为2且nodeClass为2的节点
            var newmsgY = { topic: nodeId, payload: "", actiontype: 'browse' };
            // 发送第二个类型的消息
            node.send([newmsgY, null]);
            nodeIds.push(nodeId);
        }
    }
}

// 将更新后的数组存储回flow上下文
flow.set('X_nodeIds1', X_nodeIds);
flow.set('nodeIds1', nodeIds);

// 不返回任何消息,直接通过send方法发送
return null;

请根据实际情况修改 OPC UA Browser 节点和 Delay 节点的配置,部署这个流程后。每次运行流程时,它都会遍历整个目录,将标签名保存在数组中,并将标签组名发送回 OPC UA Browser 节点进行下一次遍历。

以下是 Function 节点 (清除所有 flow 范围内的变量) 的代码(用于清除所有 flow 范围内的变量),这段代码由GPT生成:

// 清除所有 flow 范围内的变量
var keys = flow.keys(); // 获取所有存储的 keys
for (var i = 0; i < keys.length; i++) { // 遍历所有 keys
    flow.set(keys[i], undefined); // 将每个 key 对应的值设置为 undefined
}

// 验证是否清除成功
var clearedKeys = flow.keys(); // 再次获取所有存储的 keys
if (clearedKeys.length === 0) {
    node.warn("Flow context successfully cleared."); // 输出成功信息
    msg.payload = "Flow context successfully cleared."; // 设置 msg.payload 为成功信息
} else {
    node.warn("Failed to clear flow context. Remaining keys: " + clearedKeys); // 输出失败信息和剩余的 keys
    msg.payload = "Failed to clear flow context. Remaining keys: " + clearedKeys; // 设置 msg.payload 为失败信息和剩余的 keys
}

return msg; // 返回 msg

以下是 Function 节点 (Show Final Node IDs) 的代码(使用Debug输出显示所有标签名),这段代码由GPT生成:

// 获取 flow 上下文中的 nodeIds 数组
var finalNodeIds = flow.get('nodeIds1') || [];
// 将最终的 nodeIds 数组赋值给 msg.payload
msg.payload = finalNodeIds;
// 返回 msg 以显示最终的 nodeIds 数组
// return msg;

//这段代码本来到 return msg; 就结束了,但是当 数组长度超过1000时,Debug显示不出超过 1000 的元素,又增加了下边的代码改为一次最多输出1000个元素,临时解决了问题。

//   // 获取要发送的数组
//   const finalNodeIds = msg.payload;

  // 设置每次发送的元素数量
  const chunkSize = 1000;

  // 初始化起始索引和结束索引
  let startIndex = 0;
  let endIndex = chunkSize;

  // 循环发送所有元素
  while (startIndex < finalNodeIds.length) {
    // 获取当前批次的元素
    const chunk = finalNodeIds.slice(startIndex, endIndex);

    // 创建新的消息,并将当前批次的元素作为有效载荷发送
    node.send({
      payload: chunk
    });

    // 更新起始索引和结束索引,指向下一个批次
    startIndex += chunkSize;
    endIndex += chunkSize;
  }

最后,这是这个流程导出的JSON,可以直接在Node-red中尝试导入。

[
    {
        "id": "28c02a8fd3c52bbf",
        "type": "OpcUa-Browser",
        "z": "bc19e3cc51a9a215",
        "endpoint": "bfd4fb1d3827c759",
        "item": "",
        "datatype": "",
        "topic": "",
        "items": [],
        "name": "",
        "x": 530,
        "y": 60,
        "wires": [
            [
                "02fb5a0d042755b9",
                "33625a091b088e5a"
            ]
        ]
    },
    {
        "id": "e2cf37bc11c2ee19",
        "type": "inject",
        "z": "bc19e3cc51a9a215",
        "name": "Browse",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "",
        "topic": "",
        "payload": "",
        "payloadType": "str",
        "x": 100,
        "y": 60,
        "wires": [
            [
                "c209a0a4da4b3a2e",
                "f736f71918e2182c"
            ]
        ]
    },
    {
        "id": "c209a0a4da4b3a2e",
        "type": "function",
        "z": "bc19e3cc51a9a215",
        "name": "Set browse address",
        "func": "// msg.topic='ns=2;s=DL_S1.D1';\nmsg.topic='ns=0;i=85';\nmsg.actiontype='browse';\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 280,
        "y": 60,
        "wires": [
            [
                "28c02a8fd3c52bbf"
            ]
        ]
    },
    {
        "id": "b9012b8c7084df40",
        "type": "debug",
        "z": "bc19e3cc51a9a215",
        "name": "目录debug 54",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 820,
        "y": 260,
        "wires": []
    },
    {
        "id": "1bbfe2859a040f28",
        "type": "delay",
        "z": "bc19e3cc51a9a215",
        "name": "",
        "pauseType": "rate",
        "timeout": "10",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "5",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 680,
        "y": 180,
        "wires": [
            [
                "28c02a8fd3c52bbf",
                "b9012b8c7084df40"
            ]
        ]
    },
    {
        "id": "02fb5a0d042755b9",
        "type": "debug",
        "z": "bc19e3cc51a9a215",
        "name": "debug 55",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 820,
        "y": 60,
        "wires": []
    },
    {
        "id": "724b8fae9ad64826",
        "type": "debug",
        "z": "bc19e3cc51a9a215",
        "name": "变量debug 56",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 840,
        "y": 100,
        "wires": []
    },
    {
        "id": "33625a091b088e5a",
        "type": "delay",
        "z": "bc19e3cc51a9a215",
        "name": "",
        "pauseType": "rate",
        "timeout": "10",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 310,
        "y": 180,
        "wires": [
            [
                "e204381af2d34de2"
            ]
        ]
    },
    {
        "id": "e204381af2d34de2",
        "type": "function",
        "z": "bc19e3cc51a9a215",
        "name": "Decode & filter",
        "func": "// 定义两个空数组,用于存储不同类型的nodeId\nvar X_nodeIds = flow.get('X_nodeIds1') || [];\nvar nodeIds = flow.get('nodeIds1') || [];\n\n// 获取消息中的节点数组\nvar items = msg.payload;\n\n// 遍历节点数组中的每一个节点\nfor (var i = 0; i < items.length; i++) {\n    var item = items[i];\n    var ref = item.item;\n    var nodeClass = ref.nodeClass;\n    var typeDef = ref.typeDefinition;\n    var bname = ref.browseName;\n    var ns = bname.namespaceIndex;\n    var name = bname.name;\n    var nodeId = ref.nodeId;\n    var NodeId_str = nodeId.toString();\n\n    // 检查并跳过不满足条件的nodeId\n    if (NodeId_str.includes('._') || NodeId_str.match(/^.*=_/) || NodeId_str.match('.V4') || NodeId_str.match('.@')) {\n        continue;\n    }\n\n    // 根据不同的条件创建并发送消息\n    if (ns === 2) {\n        if (nodeClass === 1) { // 条件一:命名空间索引为2且nodeClass为1的节点\n            var newmsgX = { topic: nodeId, payload: \"\", actiontype: 'browse' };\n            // 发送第一个类型的消息\n            node.send([null, newmsgX]);\n            X_nodeIds.push(nodeId);\n        } else if (nodeClass === 2) { // 条件二:命名空间索引为2且nodeClass为2的节点\n            var newmsgY = { topic: nodeId, payload: \"\", actiontype: 'browse' };\n            // 发送第二个类型的消息\n            node.send([newmsgY, null]);\n            nodeIds.push(nodeId);\n        }\n    }\n}\n\n// 将更新后的数组存储回flow上下文\nflow.set('X_nodeIds1', X_nodeIds);\nflow.set('nodeIds1', nodeIds);\n\n// 不返回任何消息,直接通过send方法发送\nreturn null;",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 180,
        "wires": [
            [
                "724b8fae9ad64826"
            ],
            [
                "1bbfe2859a040f28"
            ]
        ]
    },
    {
        "id": "a14762e716eb88fd",
        "type": "inject",
        "z": "bc19e3cc51a9a215",
        "name": "Show Final IDs",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 120,
        "y": 320,
        "wires": [
            [
                "e656c50f0dcef8aa"
            ]
        ]
    },
    {
        "id": "e656c50f0dcef8aa",
        "type": "function",
        "z": "bc19e3cc51a9a215",
        "name": "Show Final Node IDs",
        "func": "// 获取 flow 上下文中的 nodeIds 数组\nvar finalNodeIds = flow.get('nodeIds1') || [];\n// 将最终的 nodeIds 数组赋值给 msg.payload\nmsg.payload = finalNodeIds;\n// 返回 msg 以显示最终的 nodeIds 数组\n// return msg;\n\n\n//   // 获取要发送的数组\n//   const finalNodeIds = msg.payload;\n\n  // 设置每次发送的元素数量\n  const chunkSize = 1000;\n\n  // 初始化起始索引和结束索引\n  let startIndex = 0;\n  let endIndex = chunkSize;\n\n  // 循环发送所有元素\n  while (startIndex < finalNodeIds.length) {\n    // 获取当前批次的元素\n    const chunk = finalNodeIds.slice(startIndex, endIndex);\n\n    // 创建新的消息,并将当前批次的元素作为有效载荷发送\n    node.send({\n      payload: chunk\n    });\n\n    // 更新起始索引和结束索引,指向下一个批次\n    startIndex += chunkSize;\n    endIndex += chunkSize;\n  }\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 320,
        "wires": [
            [
                "3fbf36f8f6fdfc80"
            ]
        ]
    },
    {
        "id": "3fbf36f8f6fdfc80",
        "type": "debug",
        "z": "bc19e3cc51a9a215",
        "name": "变量地址",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 700,
        "y": 320,
        "wires": []
    },
    {
        "id": "f736f71918e2182c",
        "type": "function",
        "z": "bc19e3cc51a9a215",
        "name": "清除所有 flow 范围内的变量",
        "func": "// 清除所有 flow 范围内的变量\nvar keys = flow.keys(); // 获取所有存储的 keys\nfor (var i = 0; i < keys.length; i++) { // 遍历所有 keys\n    flow.set(keys[i], undefined); // 将每个 key 对应的值设置为 undefined\n}\n\n// 验证是否清除成功\nvar clearedKeys = flow.keys(); // 再次获取所有存储的 keys\nif (clearedKeys.length === 0) {\n    node.warn(\"Flow context successfully cleared.\"); // 输出成功信息\n    msg.payload = \"Flow context successfully cleared.\"; // 设置 msg.payload 为成功信息\n} else {\n    node.warn(\"Failed to clear flow context. Remaining keys: \" + clearedKeys); // 输出失败信息和剩余的 keys\n    msg.payload = \"Failed to clear flow context. Remaining keys: \" + clearedKeys; // 设置 msg.payload 为失败信息和剩余的 keys\n}\n\nreturn msg; // 返回 msg",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "b7e1628c12f76bda",
        "type": "inject",
        "z": "bc19e3cc51a9a215",
        "name": "清理",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 90,
        "y": 280,
        "wires": [
            [
                "f736f71918e2182c"
            ]
        ]
    },
    {
        "id": "bfd4fb1d3827c759",
        "type": "OpcUa-Endpoint",
        "endpoint": "opc.tcp://SJ-KEPSer-01:49320",
        "secpol": "None",
        "secmode": "None",
        "none": true,
        "login": false,
        "usercert": false,
        "usercertificate": "",
        "userprivatekey": ""
    }
]

有一个问题没有解决,就是第一次点击部署,程序就开始运行,但是会出想非常多的重复标签,等程序运行完成后再次点击inject节点Browse,这次运行的结果是根预期的一样。
不知道是哪里的问题

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值