Java实现OPCUA通信
描述
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个字节。
类别 | 数据类型 | 长度(位) | 长度(字节) | 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 | ❌ | ✔️ | ✔️ |