发现驱动程序及其配置
本页面面向将 PLC4X 集成到其他工具中的开发者:例如连接对话框、配置表单、流水线中的校验步骤,或基于流程的编辑器中的节点。
这些内容无需硬编码。每个驱动都会附带一份对自身的描述,PLC4X 可以在运行时把这份描述交给你:classpath 上有哪些驱动、每个驱动支持哪些传输方式,以及每种组合接受哪些配置参数——包括参数类型、是否必填以及默认值。
这与本站协议页面]所使用的元数据完全相同,因此你的工具显示的内容与文档所述永远不会出现偏差。
本 API 属于 PLC4J(Java)。PLC4Go、PLC4Py 和 PLC4C 目前尚未提供等效能力——PLC4Go 的 GetMetadata() 描述的是一个连接,而非驱动。 |
|---|
起点在哪里
一切都挂接在 PlcDriverManager 上:
import org.apache.plc4x.java.api.PlcDriverManager;
PlcDriverManager driverManager = PlcDriverManager.getDefault();驱动是通过 Java 的 ServiceLoader 查找的,因此“我有哪些驱动”从字面上讲就是“类路径上有哪些驱动 jar”。如果你的工具把插件加载到了自己的类加载器中,请显式地把它传过来:
import org.apache.plc4x.java.DefaultPlcDriverManager;
PlcDriverManager driverManager = new DefaultPlcDriverManager(myPluginClassLoader);我有哪些驱动?
Set<String> protocolCodes = driverManager.getProtocolCodes(); (1)
PlcDriver driver = driverManager.getDriver("s7"); (2)
String humanReadable = driver.getProtocolName(); (3)| 1 | 简短代码——即 s7://10.0.0.1 中的 s7。 |
|---|---|
| 2 | 如果 classpath 中没有该代码对应的驱动,将抛出 PlcConnectionException。 |
| 3 | 显示名称,例如 Siemens S7 (Basic)。接线时用代码,给人看时用名称。 |
getProtocolCodes() 返回的是无序的 Set。在展示给任何人之前请先排序,否则你的下拉列表会在每次运行之间自行打乱顺序。 |
|---|
如果你已经有一个连接串,只是想拿到其背后的驱动,可以用 driverManager.getDriverForUrl(url),而不必自己解析 scheme。
加上 plc4j-driver-all 依赖后,上面的代码会得到 18 个驱动:
ab-eth | ads | canopen |
|---|---|---|
eip | firmata | genericcan |
iec-60870-5-104 | knxnet-ip | logix |
modbus-ascii | modbus-rtu | modbus-tcp |
opcua | plc4x | s7 |
simulated | slmp | umas |
驱动程序支持哪些传输方式?
其余内容都位于 PlcDriverMetadata:
import org.apache.plc4x.java.api.metadata.PlcDriverMetadata;
PlcDriverMetadata metadata = driver.getMetadata();
List<String> transports = metadata.getSupportedTransportCodes(); (1)
Optional<String> preferred = metadata.getDefaultTransportCode(); (2)
boolean canDiscover = metadata.isDiscoverySupported(); (3)| 1 | 例如 [tcp, tls, tls-psk, udp, test] 对应 modbus-tcp。 |
|---|---|
| 2 | 当连接字符串未指定传输方式时,驱动所使用的传输方式。请预先选中此项。 |
| 3 | 驱动是否能够在网络上搜索设备——可用它来启用“扫描”按钮。 |
有两点需要处理:
- 过滤掉
test。 它是 PLC4X 自身单元测试所使用的内存传输方式。它会出现在列表中,但并非用户可以选择的选项。本站的协议页面正是出于这一原因将其过滤掉。 - 列表可能为空。
simulated不会报告任何传输方式,因为它从不与外界通信。不要假定至少会有一个。
List<String> selectable = metadata.getSupportedTransportCodes().stream()
.filter(code -> !"test".equals(code))
.toList();驱动的配置是什么?
import org.apache.plc4x.java.api.metadata.OptionMetadata;
Optional<OptionMetadata> protocolOptions =
metadata.getProtocolConfigurationOptionMetadata();这是一个 Optional,因为驱动可能不声明任何自己的选项——simulated 就没有。
该驱动的某种传输的配置是什么?
请始终请求配对组合,而不要单独请求传输:
Optional<OptionMetadata> transportOptions =
metadata.getTransportConfigurationOptionMetadata("tcp");驱动的传输配置是按「驱动 + 传输」的组合对外暴露的,因此请始终通过你正在配置的驱动来查询该配置,而不是为每种传输代码缓存一份表。
一个配置定义了哪些参数?
OptionMetadata 会给出参数列表,Option 则会描述每一项:
import org.apache.plc4x.java.api.metadata.Option;
List<Option> all = protocolOptions.get().getOptions();
List<Option> mandatory = protocolOptions.get().getRequiredOptions(); // convenience filter| 方法 | 返回值 | 该返回值的用途 |
|---|---|---|
getKey() | String | 出现在连接字符串中的参数名称。 |
getType() | OptionType | 选择合适的控件并校验输入。见下文。 |
isRequired() | boolean | 将该字段标记为必填,为空时禁止提交。 |
getDefaultValue() | Optional<Object> | 预填充该字段。为空表示没有默认值。 |
getDescription() | String | 工具提示或帮助文本。 |
isSecret() | boolean | 渲染为密码字段,并将其排除在日志之外。 |
getSince() | Optional<String> | 引入该选项的 PLC4X 版本,例如 0.13.0。只有部分选项带有该信息。 |
OptionType 是一个小型枚举——BOOLEAN、INT、LONG、FLOAT、DOUBLE、STRING、FILE、STRUCT:
FILE表示路径——提供一个文件选择器(用于密钥库和证书)。STRUCT是驱动程序会从其字符串形式解析出的复合值,例如 ADS 的target-ams-net-id。将其视为自由文本,由驱动程序进行校验;其语法记录在驱动程序自己的协议页面上。
大多数选项都是可选的并带有默认值。ads 是默认集中唯一真正要求输入的驱动程序——四个必填参数,其中两个是 STRUCT:
target-ams-net-id STRUCT required
target-ams-port INT required
source-ams-net-id STRUCT required
source-ams-port INT required将选项转换回连接字符串
这些键将原样用作查询参数,只有一条规则:传输层选项使用传输层代码作为命名空间,协议选项则不加命名空间。
s7://10.0.0.1?pdu-size=2048&cotp.local-rack=1&cotp.remote-slot=2
^ ^ ^
| | └── transport option, prefixed with "cotp."
| └── transport option
└── protocol option, no prefix因此,在构建该字符串时,请恰好为来自 getTransportConfigurationOptionMetadata(…) 的值加上前缀:
String key = (transportCode == null) ? option.getKey()
: transportCode + "." + option.getKey();标记为 isSecret() 的任何内容都会被合并到同一个字符串中。PLC4X 在自身的日志输出中会对这些参数进行脱敏处理——在记录、显示或持久化连接字符串之前,你也应当进行同样的脱敏处理。 |
|---|
完整示例
本程序会打印完整的目录——包括每个驱动程序、其传输方式,以及每个选项的类型、是否必填和默认值:
import org.apache.plc4x.java.api.PlcDriver;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.metadata.Option;
import org.apache.plc4x.java.api.metadata.OptionMetadata;
import org.apache.plc4x.java.api.metadata.PlcDriverMetadata;
import java.util.List;
import java.util.TreeSet;
public class DriverCatalog {
public static void main(String[] args) throws Exception {
PlcDriverManager driverManager = PlcDriverManager.getDefault();
for (String protocolCode : new TreeSet<>(driverManager.getProtocolCodes())) {
PlcDriver driver = driverManager.getDriver(protocolCode);
PlcDriverMetadata metadata = driver.getMetadata();
System.out.println("== " + protocolCode + " (" + driver.getProtocolName() + ")");
System.out.println(" discovery supported : " + metadata.isDiscoverySupported());
System.out.println(" default transport : "
+ metadata.getDefaultTransportCode().orElse("<none>"));
// "test" is an in-memory transport used by PLC4X's own unit tests.
List<String> transports = metadata.getSupportedTransportCodes().stream()
.filter(code -> !"test".equals(code))
.toList();
System.out.println(" transports : " + transports);
metadata.getProtocolConfigurationOptionMetadata()
.ifPresent(options -> print(" protocol options", options, null));
for (String transportCode : transports) {
metadata.getTransportConfigurationOptionMetadata(transportCode)
.ifPresent(options ->
print(" transport options (" + transportCode + ")", options, transportCode));
}
System.out.println();
}
}
private static void print(String heading, OptionMetadata metadata, String prefix) {
System.out.println(heading + ":");
for (Option option : metadata.getOptions()) {
// In a connection string, transport options are namespaced with the transport code.
String key = (prefix == null) ? option.getKey() : prefix + "." + option.getKey();
System.out.printf(" %-34s %-8s %-9s %-7s %s%n",
key,
option.getType(),
option.isRequired() ? "required" : "optional",
option.isSecret() ? "secret" : "",
option.getDefaultValue().map(v -> "default=" + v).orElse(""));
}
}
}ADS 驱动的输出如下:
== ads (Beckhoff TwinCat ADS)
discovery supported : true
default transport : tcp
transports : [tcp]
protocol options:
target-ams-net-id STRUCT required
target-ams-port INT required
source-ams-net-id STRUCT required
source-ams-port INT required
request-timeout-ms INT optional default=4000
max-data-type-table-depth INT optional default=20
load-symbol-and-data-type-tables BOOLEAN optional default=true
transport options (tcp):
tcp.connect-timeout-ms INT optional default=5000
tcp.read-timeout-ms INT optional default=0
tcp.write-timeout-ms INT optional default=0
tcp.no-delay BOOLEAN optional default=true
tcp.keep-alive BOOLEAN optional default=false
tcp.send-buffer-size INT optional default=81920
tcp.receive-buffer-size INT optional default=81920
tcp.local-address STRING optional
tcp.local-port INT optional default=0要运行它,你需要在类路径(classpath)中包含一个驱动程序。plc4j-driver-all 会列出所有驱动程序:
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-all</artifactId>
<version>1.0.0</version>
</dependency>需要注意的事项
getProtocolCodes()是一个无序的Set——显示前请先排序。- 将
test传输方式从用户可见的任何内容中过滤掉。 - 一个驱动可能报告没有任何传输方式(
simulated),也没有任何选项(simulated再次出现)。每一个可能缺失的元数据访问器都会返回一个Optional或一个空列表——它们都不会返回null,但同样也都不保证有内容。 - 请求传输选项时应按“驱动与传输方式”的组合来查询,而不是按传输方式代码查询。
- 在任何地方都要将
isSecret()选项当作凭据处理:在 UI 中遮蔽显示,在日志中脱敏。
评论
登录后参与评论
KnowForge