当前位置:   article > 正文

【Node-RED】node-red-contrib-opcua-server模块使用(2)

【Node-RED】node-red-contrib-opcua-server模块使用(2)


前言

上期博文【Node-RED】node-red-contrib-opcua-server模块使用(1)中,我们主要介绍了,模块安装过程中遇到的相关问题。本期博文,我们介绍node-red-contrib-opcua-server模块的使用,并以官方示例中server-with-context.json流程为例展开介绍,主要涉及地址空间的配置加密设置

博文所提示例已经上传资源中,期间设置的所有相关设置都保留在流程中,可以下载使用。

示例简单介绍

示例主要由变量产生opcuaServe配置 两部分组成。

变量产生

变量产生用到了注入定时器,周期性的实现变量的生成和变化,变量主要有2部分,isoInput2-isoInput8,isoOutput2-isoOutput8,这个可以自定义,通过函数组件将变量保存到流程的上下文数据中,方便后续的使用。
在这里插入图片描述
通过点击注入,在上下文数据中可以进行数据查看,点击右上角的圆圈进行刷新,可以实时更新数据。
在这里插入图片描述

opcuaServe配置

node-red-contrib-opcua-server模块中实现opcuaServe配置,主要采用opcua-compact-server组件,在前期测试中,主要以匿名形式设置,加密可以直接跳转到后面加密设置

  • 需要勾选Setting 中的 Show Errors,方便错误信息的查看。
  • DiscoveryEndpoint Url需配置上地址,端口可以在Setting 中查看。如果Resource Path没有配置,又是本地,Endpoint Url设置为opc.tcp://localhost:54840,54840为默认端口,可以自定义更改;如果Resource Path配置了,Endpoint Url设置为opc.tcp://localhost:54840/UA/NodeRED/Compact,UA/NodeRED/Compact为默认的资源地址。

然后点击部署,当显示active 即表示成功。
在这里插入图片描述

地址空间的配置

创建opcua服务器

  const opcua = coreServer.choreCompact.opcua;
  • 1
  • 注意:爆红不影响使用
    在这里插入图片描述

获取命名空间

const namespace = addressSpace.getOwnNamespace();
  • 1

初始化变量

const Variant = opcua.Variant;
  const DataType = opcua.DataType;
  const DataValue = opcua.DataValue;

  var flexServerInternals = this;

  this.sandboxFlowContext.set("isoInput1", 0);
  this.setInterval(() => {
    flexServerInternals.sandboxFlowContext.set(
      "isoInput1",
      Math.random() + 50.0
    );
  }, 500);
  this.sandboxFlowContext.set("isoInput2", 0);
  this.sandboxFlowContext.set("isoInput3", 0);
  ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

定义文件夹

coreServer.debugLog("init dynamic address space");
  const rootFolder = addressSpace.findNode("RootFolder");

  node.warn("construct new address space for OPC UA");

  const myDevice = namespace.addFolder(rootFolder.objects, {
    "browseName": "RaspberryPI-Zero-WLAN"
  });
  const gpioFolder = namespace.addFolder(myDevice, { "browseName": "GPIO" });
  const isoInputs = namespace.addFolder(gpioFolder, {
    "browseName": "Inputs"
  });
  const isoOutputs = namespace.addFolder(gpioFolder, {
    "browseName": "Outputs"
  });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

定义文件夹中的变量

const gpioDI1 = namespace.addVariable({
    "organizedBy": isoInputs,
    "browseName": "I1",
    "nodeId": "ns=1;s=Isolated_Input1",
    "dataType": "Double",
    "value": {
      "get": function() {
        return new Variant({
          "dataType": DataType.Double,
          "value": flexServerInternals.sandboxFlowContext.get("isoInput1")
        });
      },
      "set": function(variant) {
        flexServerInternals.sandboxFlowContext.set(
          "isoInput1",
          parseFloat(variant.value)
        );
        return opcua.StatusCodes.Good;
      }
    }
  });

  const gpioDI2 = namespace.addVariable({
    "organizedBy": isoInputs,
    "browseName": "I2",
    "nodeId": "ns=1;s=Isolated_Input2",
    "dataType": "Double",
    "value": {
      "get": function() {
        return new Variant({
          "dataType": DataType.Double,
          "value": flexServerInternals.sandboxFlowContext.get("isoInput2")
        });
      },
      "set": function(variant) {
        flexServerInternals.sandboxFlowContext.set(
          "isoInput2",
          parseFloat(variant.value)
        );
        return opcua.StatusCodes.Good;
      }
    }
  });
  ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

view文件夹增加

const viewDI = namespace.addView({
    "organizedBy": rootFolder.views,
    "browseName": "RPIW0-Digital-Ins"
  });

  const viewDO = namespace.addView({
    "organizedBy": rootFolder.views,
    "browseName": "RPIW0-Digital-Outs"
  });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

view文件夹中查阅信息定义

viewDI.addReference({
    "referenceType": "Organizes",
    "nodeId": gpioDI1.nodeId
  });

  viewDI.addReference({
    "referenceType": "Organizes",
    "nodeId": gpioDI2.nodeId
  });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

最终效果

在这里插入图片描述

加密设置

opcuaServe组件配置

  1. 在Security中取消Allow Anonymous勾选
  2. 勾选Use individual Certificate Files
  3. Public填上公钥地址,Private填上私钥地址,可以直接用绝对地址,这里用了默认的相对地址..\certificates,不太对,依旧报没有路径下没有文件。
C:\Users\11003189\.node-red\node_modules\node-red-contrib-opcua-server\opcua-certificate-2048\server_selfsigned_cert_2048.pem
  • 1
C:\Users\11003189\.node-red\node_modules\node-red-contrib-opcua-server\opcua-certificate-2048\server_key_2048.pem
  • 1

在这里插入图片描述

客户端配置

  1. 将公钥的pem文件转der文件,私钥不用。
openssl x509 -in server_selfsigned_cert_2048.pem -out server_selfsigned_cert_2048.der -outform DER
  • 1

在这里插入图片描述

  1. 在客户端里面直接导入公钥和私钥文件即可。在这里插入图片描述

这里有一个问题,官网博文中说加密模式选Aes128Sha256RsaOaep,但是opcuaServe组件并不支持这个算法模式,所以最后也就没有连接成功,但是官网博文中一样的配置居然连接成功了,很迷。

参考官网博文

How to Deploy a Basic OPC-UA Server in Node-RED - Part 1

How to Build a Secure OPC-UA Server for PLCs in Node-RED

node-red-contrib-opcua-server模块的研究就先到这里了,原以为该模块的函数部分编辑可以实现二次开发,结果只能是地址空间的配置,而且该模块也没有输出。
又测试了node-red-contrib-opcua,它主要是输出连接状态、收到的写入信息,不能在服务器中对输出信息进行二次处理。
基于以上的情况,需求依旧无法实现,上周的喜悦戛然而止,不过热情依旧,继续探索Opcua 的魅力,目前也看了c++中实现Opcua的底层函数,严重怀疑估计得改底层代码,庆幸NodeRed是开源的,因此继续研究。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/218034
推荐阅读
相关标签
  

闽ICP备14008679号