视频说明:https://www.bilibili.com/video/BV1TR4y1Q71g/

描述

utgard 的方式过时了,所以建议使用 OPCUA 的方式。

安装 kep :OPCServer:使用KEPServer

这是连接操作说明:OPC UA Client:使用UaExpert

使用的开源库是 milo:https://github.com/eclipse/milo

因为没有实际项目,所以只运行 milo 的示例代码的客户端部分:

https://github.com/eclipse/milo/tree/master/milo-examples/client-examples

使用西门子的 OPC UA Server,相关文档

? S7-1200 OPC UA 通信
? S7-1500 OPC UA服务器S7-1500 OPC UA客户端

代码

Github:https://github.com/ioufev/opcua-milo-demo

蓝奏云:https://ioufev.lanzout.com/i1S7K0kt4dda

过程和问题??

证书问题

❓ 问题:运行报证书问题
⚓ 描述:java.io.IOException: parseAlgParameters failed: ObjectIdentifier() — data isn’t an object ID (tag = 48)
? JDK 的版本问题,升级 jdk8 到升级到 1.8.0.301及以上,或者使用 jdk11 或 17 运行。参考

❓ 问题:生成证书不带 URI
⚓ 描述:在 windows 上使用 openssl 生成带 URI 信息的自签名证书,没找到操作说明。在 linux 上看教程到还可以。
? 想到:不自己生成证书,使用 milo 的证书处理类先生成一个证书,以后使用这个生成的证书。

如图,生成证书

主机名未知

❓ 问题:不使用匿名连接,只能连接本地,不能连接远程。
⚓ 描述:可以使用 UaExpert 的用户名密码连接,milo 代码测试不能连接,报 UnknownHostException 即 主机名未知 错误。
? 搜索到相关解答,服务发现时,服务端返回的断点描述的主机名或者本地IP,远程是访问不到的,按照解答参考,修改端点的主机名为远程IP地址即可。

没有选择节点

❓ 问题:no endpoit selected
⚓ 描述:使用默认代码运行,报没有选择节点
? 服务端可以配置连接方式,安全策略和安全模式

我的代码用的是
安全策略:Basic256Sha256
安全模式:签名并加密

OPC UA 服务端配置的安全策略和安全模式,可能和我的测试代码不一致,需要自己修改。

安全策略和安全模式

代码修改安全策略

一些理解

OPCUA 官方内容

OPCUA 规范:https://reference.opcfoundation.org/

内容太多,很繁琐,还分好多部分。感觉看看配的图片就行了。

机器翻译后的内容

? OPCUA 规范 第 1 部分:概述和概念 5:概述
? OPCUA 规范 第 1 部分:概述和概念 6:系统概念

可以不使用 OPCUA 这种方式连接吗?

是可以的,很多 PLC 使用的协议是公开的,比如 Modbus,直接连接也没问题。

DA 到 UA

OPC DA 是针对 windows DCOM 的规范,以后肯定不推荐了。

OPC UA 要兼容 DA,但是 要摆脱 windows DCOM,所以推出类似 HTTP 的 opc over tcp 协议。

旧的项目,第三方 OPCserver,比如 kep ,去连接设备获取数据,kep 提供不同的连接方式(DA、UA、ThingWorx)

除非使用的第三方 OPC 只支持 DA,但是感觉这样的 OPCserver 该被淘汰了。

OPCUA 的连接

看别人写的帖子都使用无安全策略的连接方式,是很省事。

我对证书的内容,不太理解明白,所以后续会补充内容。

? 试过之后,不建议自己生成证书,使用 milo 的证书加载类生成证书非常合适

? 生成简单的自签名证书(以前测试用的,现在不需要了):Windows 安装 OpenSSL 生成自签名证书

OPCUA 和物模型,和 Java 对象类比

OPCUA 是一种映射方式,非常像 Java 中使用类描述对象。

按照所谓 “物模型” 的说法,设备就是一个对象,

? 设备的参数,就是:物模型的属性值,Java 中类的属性(也可以叫变量,字段),OPCUA 中的节点的变量。

? 设备的操作方法,就是:物模型的功能,Java 中类的方法(也可以叫函数),OPCUA 中的方法

? 设备的出现的各种状况(比如上线,某个组件出故障,某个参数超标),就是:物模型的事件,Java 中的事件,OPCUA中订阅。

对于事件的理解,感觉很像 MQTT 中的发布订阅,如果设备发生了什么故障,把情况通知到订阅的人。

去年做了一个无人船项目,项目不太成功,不过可以来具体举例理解。

? 无人船运行过程中,需要知道运行状态:电池的温度、电流电压、剩余电量,船的速度,GPS 坐标,航向角等。
? 无人船要能远程控制,通过摄像头获取到远程视频,能在界面上控制船前进、加速、转弯、后退、停止。
? 无人船航行过程中发现有人在游泳,或者电池快没电了发出提示,或者航行到了水质参数异常的区域发出提示。

? OPCUA 中的引用,和 Java 中一个类引用另一个类的实例作为属性值,很相似。

? OPCUA 的节点类,和 Java 中的类也很相似,节点是从根节点到层层子节点,Java 中也是从 Object 类开始加载。

? 地址空间,一个树形结构,每个节点是一个类,每次看地址空间,感觉就像在 idea 里看 Java 类的结构。

OPCUA 中的数据类型

OPCUA 中的数据类型,连 Java 中的 null 都有对应。

Boolean、
Byte、
ByteString:使用字节定义字符串,感觉和 Java9 中 String 的定义由 char[] 改为 byte[] 很像。
DateTime、
Double、
Float、
Int16、Int32、Int64
UInt16、UInt32、UInt64:无符号类型,没有用一个位表示正负号,只表示零和正数。

? milo 中 Unsigned 类封装了无符号类型的表示,比如 Uint16 类型的 12,表示为:Unsigned.ushort(12)

String

节点标识符

OPCUA 中的 Identifier,节点标识符,milo 中 Identifiers 类定义的,

对于想要读取的项,比如 “通道 1.设备 1. 标记 1”,这就是一个 Identifier

OPCUA 的订阅

MQTT 中的订阅,是要有主题的。

? OPCUA 的订阅是个事件通知,比如订阅某个变量的值如果超出某个范围,触发事件,发出通知。

请求响应 vs 发布订阅

也可以叫 OPCUA vs MQTT

OPCUA 是个发展的协议,原来就是请求响应模式,

所以大部人使用都是:OPCUA获取到数据后通过MQTT发送出去。

估计OPCUA的有些人觉得不爽,觉得OPCUA也要有发布订阅模式,我看 UAExpert 也有了发布订阅功能,不过还没见人使用,因为 MQTT 的发布订阅很方便。

使用 KEPServerEX:把 OPC 数据通过 MQTT 上传

OPCUA 的通信协议

原来的 OPC 只是个规范,OPCUA 有个基于 TCP 的应用层是二进制格式的协议,即常见的 opc.tcp://

OPCUA 的通信协议,原来似乎是 XML 格式,后来这种模式被 JSON 格式取代了,OPCUA 也与时俱进。

OPCUA 是个应用层协议,使用 TCP 传输,加密传输就是 TCP + TLS。

MQTT 也可使用 WebSocket 作为传输层,传输 MQTT 格式的信息,OPCUA 也可以使用 WebSocket 作为传输层,也就是浏览器作为OPCUA客户端,直接访问OPCUA服务端,暂时还没看到有实现开源库。

OPCUA 的建模

操作就是类似在kep建项:标记1、标记2、标记3。。。
然后保存成文件。

感觉就是:用 XML 格式或者 JSON 格式,来描述服务端有什么节点,节点有什么属性。

在服务端定义节点,如果项少,自然没问题。如果项很多,也就是节点很多。

如果行业中有人定义好了拿出来分享,感觉就是所谓的建模。

补充内容

地址说明

内容来自 kep 关于 OPC UA Client 的帮助文件

Client 驱动程序 地址的语法如下: ns=<namespace index>;<type>=<value>。有关详细信息,请参阅下表。

字段 说明
命名空间索引 地址所在的 OPC UA 服务器命名空间的索引。如果索引为 0,则省略整个 ns =<namespace index="">;</namespace> 子句。
类型 地址类型。OPC UA 支持以下四种地址类型:
i: 用 32 位无符号整数表示的数字地址
s: 由 UTF-8 编码字符Closed有符号 8 位值。组成的字符串地址
g: 采用 {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} 格式的 GUID 地址
b: 不透明的地址 (例如: 字节字符串)
格式化为字符串的地址。此地址可以是数字、字符串、GUID 或不透明。

示例

地址类型 名称空间 示例
数字 2 ns=2;i=13
字符串 3 ns=3;s=Channel1.Device1.Tag1
GUID 0 g=
不透明 2 ns=2;b=M/RbKBsRVkePCePcx24oRA==

milo 代码中节点类 NodeId

public final class NodeId {

    public static final NodeId NULL_NUMERIC = new NodeId(ushort(0), uint(0));
    public static final NodeId NULL_STRING = new NodeId(ushort(0), "");
    public static final NodeId NULL_GUID = new NodeId(ushort(0), new UUID(0, 0));
    public static final NodeId NULL_OPAQUE = new NodeId(ushort(0), ByteString.NULL_VALUE);

    public static final NodeId NULL_VALUE = NULL_NUMERIC;

    private final UShort namespaceIndex;
    private final Object identifier;


    public NodeId(int namespaceIndex, int identifier) {
        this(ushort(namespaceIndex), uint(identifier));
    }


    public NodeId(int namespaceIndex, UInteger identifier) {
        this(ushort(namespaceIndex), identifier);
    }


    public NodeId(int namespaceIndex, String identifier) {
        this(ushort(namespaceIndex), identifier);
    }


    public NodeId(int namespaceIndex, UUID identifier) {
        this(ushort(namespaceIndex), identifier);
    }


    public NodeId(int namespaceIndex, ByteString identifier) {
        this(ushort(namespaceIndex), identifier);
    }


    public NodeId(UShort namespaceIndex, UInteger identifier) {
        checkNotNull(namespaceIndex);
        checkNotNull(identifier);

        this.namespaceIndex = namespaceIndex;
        this.identifier = identifier;
    }


    public NodeId(UShort namespaceIndex, int identifier) {
        checkNotNull(namespaceIndex);

        this.namespaceIndex = namespaceIndex;
        this.identifier = uint(identifier);
    }


    public NodeId(UShort namespaceIndex, String identifier) {
        checkNotNull(namespaceIndex);

        if (identifier == null) identifier = "";

        this.namespaceIndex = namespaceIndex;
        this.identifier = identifier;
    }


    public NodeId(UShort namespaceIndex, UUID identifier) {
        checkNotNull(namespaceIndex);
        checkNotNull(identifier);

        this.namespaceIndex = namespaceIndex;
        this.identifier = identifier;
    }


    public NodeId(UShort namespaceIndex, ByteString identifier) {
        checkNotNull(namespaceIndex);
        checkNotNull(identifier);

        this.namespaceIndex = namespaceIndex;
        this.identifier = identifier;
    }

    NodeId(@NotNull UShort namespaceIndex, @NotNull Object identifier) {
        checkNotNull(namespaceIndex);
        checkNotNull(identifier);

        this.namespaceIndex = namespaceIndex;
        this.identifier = identifier;
    }

    ...

KEPServerEX 中的数据类型

由于 KEPServerEX 是 C++ 语言写的,所以 KEPServerEX 中的数据类型和 Java 对比有些不同。

KEPServerEX 数据类型说明来自其 OPC UA Client 的帮助文件

数据类型 说明
布尔型 单个位
字节 无符号 8 位值
 
位 0 是低位
位 7 是高位
字符 有符号 8 位值
 
位 0 是低位
位 6 是高位
位 7 是符号位
日期 日期 YYYY-MM-DDTHH:MM:SS.MMM
双精度 64 位浮点值
双字型 无符号 32 位值
 
位 0 是低位
位 31 是高位
浮点型 32 位浮点值
 
驱动程序将两个连续 16 位寄存器解释为浮点值,方法是将第二个寄存器作为高位字,将第一个寄存器作为低位字。
长整型 有符号 32 位值
 
位 0 是低位
位 30 是高位
位 31 是符号位
LongLong 有符号 64 位值
 
位 0 是低位
位 62 是高位
位 63 是符号位
四字型 无符号 64 位值
 
位 0 是低位
位 63 是高位
无符号 16 位值
 
位 0 是低位
位 15 是高位
短整型 有符号 16 位值
 
位 0 是低位
位 14 是高位
位 15 是符号位
字符串 零终止字符数组

Java 中是没有 LongLong 这种类型的,这是 C++ 中的类型。

可以看出上面的数据类型都是小端(Little endian,低地址存储低字节数),与常用的大端(big endian,低地址存放高字节数)不同。

TCP/IP协议,RFC1700 规定使用“大端”字节序为网络字节序,处理器中常用“小端”。

对照表

KEPServerEX 中的类型 Java中的类型 milo代码中的类型 UAExpert中的类型写法
字符串 String
布尔型 boolean boolean
字符 char char
字节 byte byte Int8
短整形 short short Int16
ushort UInt16
长整形 int Int32
双字 UInt32
浮点型 float
双精度 double
LLong long Int64
QWord UInt64

milo 库中的数据类型存在 BuiltinDataType 类中

public enum BuiltinDataType {

    Boolean(1, Boolean.class),
    SByte(2, Byte.class),
    Byte(3, UByte.class),
    Int16(4, Short.class),
    UInt16(5, UShort.class),
    Int32(6, Integer.class),
    UInt32(7, UInteger.class),
    Int64(8, Long.class),
    UInt64(9, ULong.class),
    Float(10, Float.class),
    Double(11, Double.class),
    String(12, String.class),
    DateTime(13, DateTime.class),
    Guid(14, UUID.class),
    ByteString(15, ByteString.class),
    XmlElement(16, XmlElement.class),
    NodeId(17, NodeId.class),
    ExpandedNodeId(18, org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId.class),
    StatusCode(19, StatusCode.class),
    QualifiedName(20, QualifiedName.class),
    LocalizedText(21, org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText.class),
    ExtensionObject(22, ExtensionObject.class),
    DataValue(23, org.eclipse.milo.opcua.stack.core.types.builtin.DataValue.class),
    Variant(24, Variant.class),
    DiagnosticInfo(25, org.eclipse.milo.opcua.stack.core.types.builtin.DiagnosticInfo.class);

...

S7-1200 PLC的数据类型

参考:S7-1200数据类型
参考:S7-1200 全局DB中的数据类型介绍

SINT,有符号短整型,1个字节;
USINT,无符号短整型,1个字节;
INT,有符号整型,2个字节;
UINT,无符号整型,2个字节;
DINT,有符号双整型,4个字节;
UDINT,无符号双整型,4个字节;
LINT,有符号长整型,8个字节;
ULINT,无符号长整型,8个字节。

参考:西门子PLC内部的数据类型大全

类别 数据类型 长度(位) 长度(字节) S7-300/400 S7-1200 S7-1500
二进制 BOOL 1 1/8 ✔️ ✔️ ✔️
二进制 BYTE 8 1 ✔️ ✔️ ✔️
二进制 WORD 16 2 ✔️ ✔️ ✔️
二进制 DWORD 32 4 ✔️ ✔️ ✔️
二进制 LWORD 64 8 ✔️
整数 SINT 8 1 ✔️ ✔️
整数 INT 16 2 ✔️ ✔️ ✔️
整数 DINT 32 4 ✔️ ✔️ ✔️
整数 USINT 8 1 ✔️ ✔️
整数 UINT 16 2 ✔️ ✔️
整数 UDINT 32 4 ✔️ ✔️
整数 LINT 64 8 ✔️
整数 ULINT 64 8 ✔️
浮点数 REAL 32 4 ✔️ ✔️ ✔️
浮点数 LREAL 64 8 ✔️ ✔️
定时器 S5TIME 16 2 ✔️ ✔️
定时器 TIME 32 4 ✔️ ✔️ ✔️
定时器 LTIME 64 8 ✔️
日期和时间 DATE 16 2 ✔️ ✔️ ✔️
日期和时间 TIME_OF_DAY (TOD) 32 4 ✔️ ✔️ ✔️
日期和时间 LTOD (LTIME_OF_DAY) 64 8 ✔️
日期和时间 DT (DATE_AND_TIME) 64 8 ✔️ ✔️
日期和时间 LDT 64 8 ✔️
日期和时间 DTL 96 12 ✔️ ✔️
字符串 CHAR 8 1 ✔️ ✔️ ✔️
字符串 WCHAR 16 2 ✔️ ✔️
字符串 STRING * * ✔️ ✔️ ✔️
字符串 WSTRING * * ✔️ ✔️
PLC 数据类型 (UDT) PLC 数据类型 (UDT) * * ✔️ ✔️ ✔️
匿名结构 STRUCT * * ✔️ ✔️ ✔️
ARRAY ARRAY [….] of <数据类型> * * ✔️ ✔️ ✔️
指针 POINTER 48 6 ✔️ ✔️
指针 ANY 80 10 ✔️ ✔️
指针 VARIANT * * ✔️ ✔️
参数类型 TIMER 16 2 ✔️ ✔️
参数类型 COUNTER 16 2 ✔️ ✔️
参数类型 BLOCK_FC 16 2 ✔️ ✔️
参数类型 BLOCK_FB 16 2 ✔️ ✔️
参数类型 BLOCK_DB 16 2 ✔️
参数类型 BLOCK_SDB 16 2 ✔️
参数类型 BLOCK_SFB 16 2 ✔️
参数类型 BLOCK_SFC 16 2 ✔️
参数类型 BLOCK_OB 16 2 ✔️ ✔️ ✔️
参数类型 VOID * * ✔️ ✔️ ✔️
参数类型 PARAMETER * * ✔️ ✔️
系统数据类型 IEC_TIMER 128 16 ✔️ ✔️ ✔️
系统数据类型 IEC_LTIMER 256 32 ✔️
系统数据类型 IEC_SCOUNTER 24 3 ✔️ ✔️
系统数据类型 IEC_USCOUNTER 24 3 ✔️ ✔️
系统数据类型 IEC_COUNTER 48 6 ✔️ ✔️ ✔️
系统数据类型 IEC_UCOUNTER 48 6 ✔️ ✔️
系统数据类型 IEC_DCOUNTER 96 12 ✔️ ✔️
系统数据类型 IEC_UDCOUNTER 96 12 ✔️ ✔️
系统数据类型 IEC_LCOUNTER 192 24 ✔️
系统数据类型 IEC_ULCOUNTER 192 24 ✔️
系统数据类型 ERROR_STRUCT(ERRORSTRUCT) 224 28 ✔️ ✔️
系统数据类型 NREF 64 8 ✔️ ✔️
系统数据类型 CREF 64 8 ✔️ ✔️
系统数据类型 VREF 96 12 ✔️ ✔️
系统数据类型 SSL_HEADER 32 4 ✔️
系统数据类型 CONDITIONS 416 52 ✔️
系统数据类型 TADDR_Param 64 8 ✔️ ✔️
系统数据类型 TCON_Param 512 64 ✔️ ✔️
系统数据类型 HSC_Period 96 12 ✔️
硬件数据类型 REMOTE 80 10 ✔️ ✔️
硬件数据类型 HW_ANY 16 2 ✔️ ✔️
硬件数据类型 HW_DEVICE 16 2 ✔️ ✔️
硬件数据类型 HW_DPMASTER 16 2 ✔️
硬件数据类型 HW_DPSLAVE 16 2 ✔️ ✔️
硬件数据类型 HW_IO 16 2 ✔️ ✔️
硬件数据类型 HW_IOSYSTEM 16 2 ✔️ ✔️
硬件数据类型 HW_SUBMODULE 16 2 ✔️ ✔️
硬件数据类型 HW_MODULE 16 2 ✔️
硬件数据类型 HW_INTERFACE 16 2 ✔️ ✔️
硬件数据类型 HW_IEPORT 16 2 ✔️ ✔️
硬件数据类型 HW_HSC 16 2 ✔️ ✔️
硬件数据类型 HW_PWM 16 2 ✔️ ✔️
硬件数据类型 HW_PTO 16 2 ✔️ ✔️
硬件数据类型 AOM_IDENT 32 4 ✔️ ✔️
硬件数据类型 EVENT_ANY 32 4 ✔️ ✔️
硬件数据类型 EVENT_ATT 32 4 ✔️ ✔️
硬件数据类型 EVENT_HWINT 32 4 ✔️ ✔️
硬件数据类型 OB_ANY 16 2 ✔️ ✔️
硬件数据类型 OB_DELAY 16 2 ✔️ ✔️
硬件数据类型 OB_TOD 16 2 ✔️ ✔️
硬件数据类型 OB_CYCLIC 16 2 ✔️ ✔️
硬件数据类型 OB_ATT 16 2 ✔️ ✔️
硬件数据类型 OB_PCYCLE 16 2 ✔️ ✔️
硬件数据类型 OB_HWINT 16 2 ✔️ ✔️
硬件数据类型 OB_DIAG 16 2 ✔️ ✔️
硬件数据类型 OB_TIMEERROR 16 2 ✔️ ✔️
硬件数据类型 OB_STARTUP 16 2 ✔️ ✔️
硬件数据类型 PORT 16 2 ✔️ ✔️
硬件数据类型 RTM 16 2 ✔️ ✔️
硬件数据类型 PIP 16 2 ✔️
硬件数据类型 CONN_ANY 16 2 ✔️ ✔️
硬件数据类型 CONN_PRG 16 2 ✔️ ✔️
硬件数据类型 CONN_OUC 16 2 ✔️ ✔️
硬件数据类型 CONN_R_ID 32 4 ✔️
硬件数据类型 DB_ANY 16 2 ✔️ ✔️
硬件数据类型 DB_WWW 16 2 ✔️ ✔️
硬件数据类型 DB_DYN 16 2 ✔️ ✔️
版权声明:本文为云逸原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ioufev/p/16782761.html